Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ElasticLib Tab Selection #163

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions elasticlib/Elastic.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@
import edu.wpi.first.networktables.StringTopic;

public final class Elastic {
private static final StringTopic topic =
private static final StringTopic notificationTopic =
NetworkTableInstance.getDefault().getStringTopic("/Elastic/RobotNotifications");
private static final StringPublisher publisher =
topic.publish(PubSubOption.sendAll(true), PubSubOption.keepDuplicates(true));
private static final StringPublisher notificationPublisher =
notificationTopic.publish(PubSubOption.sendAll(true), PubSubOption.keepDuplicates(true));
private static final StringTopic selectedTabTopic =
NetworkTableInstance.getDefault().getStringTopic("/Elastic/SelectedTab");
private static final StringPublisher selectedTabPublisher =
selectedTabTopic.publish(PubSubOption.keepDuplicates(true));
private static final ObjectMapper objectMapper = new ObjectMapper();

/**
Expand All @@ -28,12 +32,35 @@ public final class Elastic {
*/
public static void sendNotification(Notification notification) {
try {
publisher.set(objectMapper.writeValueAsString(notification));
notificationPublisher.set(objectMapper.writeValueAsString(notification));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}

/**
* Selects the tab of the dashboard with the given name. If no tab matches the name, this will
* have no effect on the widgets or tabs in view.
*
* <p>If the given name is a number, Elastic will select the tab whose index equals the number
* provided.
*
* @param tabName the name of the tab to select
*/
public static void selectTab(String tabName) {
selectedTabPublisher.set(tabName);
}

/**
* Selects the tab of the dashboard at the given index. If this index is greater than or equal to
* the number of tabs, this will have no effect.
*
* @param tabIndex the index of the tab to select.
*/
public static void selectTab(int tabIndex) {
selectTab(Integer.toString(tabIndex));
}

/**
* Represents an notification object to be sent to the Elastic dashboard. This object holds
* properties such as level, title, description, display time, and dimensions to control how the
Expand All @@ -59,8 +86,8 @@ public static class Notification {
private double height;

/**
* Creates a new Notification with all default parameters. This constructor is intended
* to be used with the chainable decorator methods
* Creates a new Notification with all default parameters. This constructor is intended to be
* used with the chainable decorator methods
*
* <p>Title and description fields are empty.
*/
Expand Down
15 changes: 15 additions & 0 deletions elasticlib/elasticlib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "elasticlib.h"

#include <exception>
#include <string>

#include <fmt/core.h>
#include <networktables/NetworkTableInstance.h>
Expand Down Expand Up @@ -50,4 +51,18 @@ void SendNotification(const Notification& notification) {
}
}

void SelectTab(std::string_view tabName) {
static nt::StringTopic topic =
nt::NetworkTableInstance::GetDefault().GetStringTopic(
"/Elastic/SelectedTab");
static nt::StringPublisher publisher =
topic.Publish({.keepDuplicates = true});

publisher.Set(tabName);
}

void SelectTab(int tabIndex) {
SelectTab(std::to_string(tabIndex));
}

} // namespace elastic
19 changes: 19 additions & 0 deletions elasticlib/elasticlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,23 @@ struct Notification {
*/
void SendNotification(const Notification& notification);

/**
* Selects the tab of the dashboard with the given name. If no tab matches the
* name, this will have no effect on the widgets or tabs in view.
*
* If the given name is a number, Elastic will select the tab whose index
* equals the number provided.
*
* @param tabName the name of the tab to select
*/
void SelectTab(std::string_view tabName);

/**
* Selects the tab of the dashboard at the given index. If this index is greater
* than or equal to the number of tabs, this will have no effect.
*
* @param tabIndex the index of the tab to select.
*/
void SelectTab(int tabIndex);

} // namespace elastic
58 changes: 49 additions & 9 deletions elasticlib/elasticlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ def __init__(
self.height = height


__topic = None
__publisher = None
__selected_tab_topic = None
__selected_tab_publisher = None

__notification_topic = None
__notification_publisher = None


def send_notification(notification: Notification):
Expand All @@ -56,18 +59,20 @@ def send_notification(notification: Notification):
Raises:
Exception: If there is an error during serialization or publishing the notification.
"""
global __topic
global __publisher
global __notification_topic
global __notification_publisher

if not __topic:
__topic = NetworkTableInstance.getDefault().getStringTopic(
if not __notification_topic:
__notification_topic = NetworkTableInstance.getDefault().getStringTopic(
"/Elastic/RobotNotifications"
)
if not __publisher:
__publisher = __topic.publish(PubSubOptions(sendAll=True, keepDuplicates=True))
if not __notification_publisher:
__notification_publisher = __notification_topic.publish(
PubSubOptions(sendAll=True, keepDuplicates=True)
)

try:
__publisher.set(
__notification_publisher.set(
json.dumps(
{
"level": notification.level,
Expand All @@ -81,3 +86,38 @@ def send_notification(notification: Notification):
)
except Exception as e:
print(f"Error serializing notification: {e}")


def select_tab(tab_name: str):
"""
Selects the tab of the dashboard with the given name.
If no tab matches the name, this will have no effect on the widgets or tabs in view.
If the given name is a number, Elastic will select the tab whose index equals the number provided.

Args:
tab_name (str) the name of the tab to select
"""
global __selected_tab_topic
global __selected_tab_publisher

if not __selected_tab_topic:
__selected_tab_topic = NetworkTableInstance.getDefault().getStringTopic(
"/Elastic/SelectedTab"
)
if not __selected_tab_publisher:
__selected_tab_publisher = __selected_tab_topic.publish(
PubSubOptions(keepDuplicates=True)
)

__selected_tab_publisher.set(tab_name)


def select_tab(tab_index: int):
"""
Selects the tab of the dashboard at the given index.
If this index is greater than or equal to the number of tabs, this will have no effect.

Args:
tab_index (int) the index of the tab to select
"""
select_tab(str(tab_index))
22 changes: 19 additions & 3 deletions lib/pages/dashboard_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import 'package:window_manager/window_manager.dart';

import 'package:elastic_dashboard/services/app_distributor.dart';
import 'package:elastic_dashboard/services/elastic_layout_downloader.dart';
import 'package:elastic_dashboard/services/elasticlib_listener.dart';
import 'package:elastic_dashboard/services/hotkey_manager.dart';
import 'package:elastic_dashboard/services/ip_address_util.dart';
import 'package:elastic_dashboard/services/log.dart';
import 'package:elastic_dashboard/services/nt_connection.dart';
import 'package:elastic_dashboard/services/robot_notifications_listener.dart';
import 'package:elastic_dashboard/services/settings.dart';
import 'package:elastic_dashboard/services/shuffleboard_nt_listener.dart';
import 'package:elastic_dashboard/services/update_checker.dart';
Expand Down Expand Up @@ -70,7 +70,7 @@ class DashboardPage extends StatefulWidget {

class _DashboardPageState extends State<DashboardPage> with WindowListener {
SharedPreferences get preferences => widget.preferences;
late final RobotNotificationsListener _robotNotificationListener;
late final ElasticLibListener _robotNotificationListener;
late final ElasticLayoutDownloader _layoutDownloader;

bool _seenShuffleboardWarning = false;
Expand Down Expand Up @@ -148,6 +148,7 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
ntConnection: widget.ntConnection,
preferences: widget.preferences,
onTabChanged: (tab) {
_showShuffleboardWarningMessage();
int? parsedTabIndex = int.tryParse(tab);

bool isIndex = parsedTabIndex != null;
Expand Down Expand Up @@ -249,8 +250,23 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
() => _checkForUpdates(notifyIfLatest: false, notifyIfError: false));
}

_robotNotificationListener = RobotNotificationsListener(
_robotNotificationListener = ElasticLibListener(
ntConnection: widget.ntConnection,
onTabSelected: (tabIdentifier) {
if (tabIdentifier is int) {
if (tabIdentifier >= _tabData.length) {
return;
}
setState(() => _currentTabIndex = tabIdentifier);
} else if (tabIdentifier is String) {
int tabIndex =
_tabData.indexWhere((tab) => tab.name == tabIdentifier);
if (tabIndex == -1) {
return;
}
setState(() => _currentTabIndex = tabIndex);
}
},
onNotification: (title, description, icon, time, width, height) {
setState(() {
ColorScheme colorScheme = Theme.of(context).colorScheme;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import 'package:dot_cast/dot_cast.dart';

import 'package:elastic_dashboard/services/nt_connection.dart';

class RobotNotificationsListener {
class ElasticLibListener {
bool _alertFirstRun = true;
final NTConnection ntConnection;
final Function(String title, String description, Icon icon,
Duration displayTime, double width, double? height) onNotification;
final Function(Object tabIdentifier) onTabSelected;

RobotNotificationsListener({
ElasticLibListener({
required this.ntConnection,
required this.onNotification,
required this.onTabSelected,
});

void listen() {
Expand All @@ -27,6 +29,14 @@ class RobotNotificationsListener {
_onAlert(alertData, alertTimestamp);
});

var tabSelection = ntConnection.subscribe('/Elastic/SelectedTab', 0.2);
tabSelection.listen((tabData, _) {
if (tabData == null || tabData is! String) {
return;
}
onTabSelected(int.tryParse(tabData) ?? tabData);
});

ntConnection.addDisconnectedListener(() => _alertFirstRun = true);
}

Expand Down
13 changes: 3 additions & 10 deletions lib/services/shuffleboard_nt_listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ class ShuffleboardNTListener {

late NT4Subscription selectedSubscription;

String? previousSelection;

Map<String, Map<String, dynamic>> currentJsonData = {};

late final NetworkTableTreeRow shuffleboardTreeRoot = NetworkTableTreeRow(
Expand All @@ -46,29 +44,24 @@ class ShuffleboardNTListener {

void initializeListeners() {
selectedSubscription.addListener(() {
if (selectedSubscription.value is! String?) {
if (selectedSubscription.value == null ||
selectedSubscription.value is! String) {
return;
}

if (selectedSubscription.value != null) {
_handleTabChange(selectedSubscription.value! as String);
}

previousSelection = selectedSubscription.value! as String;
_handleTabChange(selectedSubscription.value! as String);
});

// Also clear data when connected in case if threads auto populate json after disconnection
// Chances are low since the timing has to be just right but you never know
ntConnection.addConnectedListener(() {
currentJsonData.clear();
shuffleboardTreeRoot.clearRows();
previousSelection = null;
});

ntConnection.addDisconnectedListener(() {
currentJsonData.clear();
shuffleboardTreeRoot.clearRows();
previousSelection = null;
});

ntConnection.addTopicAnnounceListener((topic) async {
Expand Down
Loading