asab_webui_shell 27.1.5 → 27.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/actions.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.SET_SUBTITLE = exports.SET_ROUTES = exports.SET_NAVIGATION_ITEMS = exports.SET_HELP_PATH = exports.SET_HEADER_NAVIGATION_ITEMS = exports.SET_FULLSCREEN_MODE = exports.SET_ATTENTION_REQUIRED_BEACON = exports.SET_ADVANCED_MODE = exports.DEL_TOAST = exports.DEL_ALERT = exports.COLLAPSE_SIDEBAR = exports.CHANGE_CONFIG = exports.ADD_TOAST = exports.ADD_ALERT = exports.ACK_ALERT = void 0;
6
+ exports.SET_SUBTITLE = exports.SET_ROUTES = exports.SET_NAVIGATION_ITEMS = exports.SET_HELP_PATH = exports.SET_HEADER_NAVIGATION_ITEMS = exports.SET_FULLSCREEN_MODE = exports.SET_CONNECTIVITY_STATUS = exports.SET_ATTENTION_REQUIRED_BEACON = exports.SET_ADVANCED_MODE = exports.DEL_TOAST = exports.DEL_ALERT = exports.COLLAPSE_SIDEBAR = exports.CHANGE_CONFIG = exports.ADD_TOAST = exports.ADD_ALERT = exports.ACK_ALERT = void 0;
7
7
  var ADD_ALERT = exports.ADD_ALERT = "asab/addAlert";
8
8
  var ACK_ALERT = exports.ACK_ALERT = "asab/ackAlert";
9
9
  var DEL_ALERT = exports.DEL_ALERT = "asab/delAlert";
@@ -18,4 +18,5 @@ var COLLAPSE_SIDEBAR = exports.COLLAPSE_SIDEBAR = "asab/collapseSidebar";
18
18
  var SET_NAVIGATION_ITEMS = exports.SET_NAVIGATION_ITEMS = "asab/setNavigationItems";
19
19
  var SET_ROUTES = exports.SET_ROUTES = "asab/setRoutes";
20
20
  var SET_HEADER_NAVIGATION_ITEMS = exports.SET_HEADER_NAVIGATION_ITEMS = "asab/setHeaderNavigationItems";
21
+ var SET_CONNECTIVITY_STATUS = exports.SET_CONNECTIVITY_STATUS = "asab/setConnectivityStatus";
21
22
  var SET_ATTENTION_REQUIRED_BEACON = exports.SET_ATTENTION_REQUIRED_BEACON = "asab/setAttentionRequiredBeacon";
@@ -80,6 +80,10 @@ class Application extends _react.Component {
80
80
  // If more than zero, some networking activity is happening
81
81
  splashscreenRequestors: 0
82
82
  };
83
+
84
+ // Subscribe and unsubscribe handlers for connectivity detection
85
+ this._initConnectivitySubscription = this._initConnectivitySubscription.bind(this);
86
+ this._unsubscribeConnectivity = null;
83
87
  this.ConfigService.addDefaults(props.configdefaults);
84
88
  this.addSplashScreenRequestor(this);
85
89
  this.state.splashscreenRequestors = this.SplashscreenRequestors.size;
@@ -442,6 +446,9 @@ class Application extends _react.Component {
442
446
  componentDidMount() {
443
447
  document.addEventListener("keyup", this._handleKeyUp, false);
444
448
 
449
+ // Subscribe to Application.status! once PubSub is available
450
+ this._initConnectivitySubscription();
451
+
445
452
  // Add print-landscape class to body if not present
446
453
  if (!document.body.classList.contains('print-landscape')) {
447
454
  document.body.classList.add('print-landscape');
@@ -449,6 +456,12 @@ class Application extends _react.Component {
449
456
  }
450
457
  componentWillUnmount() {
451
458
  document.removeEventListener("keyup", this._handleKeyUp, false);
459
+
460
+ // Unsubscribe from Application.status! PubSub
461
+ if (this._unsubscribeConnectivity) {
462
+ this._unsubscribeConnectivity();
463
+ this._unsubscribeConnectivity = null;
464
+ }
452
465
  }
453
466
 
454
467
  // Splash screen
@@ -648,4 +661,29 @@ class Application extends _react.Component {
648
661
  }))));
649
662
  }
650
663
  }
664
+
665
+ /*
666
+ On Application initialization, initialize subscription to Application.status!
667
+ once PubSub is assigned by PubSubProvider
668
+ */
669
+ Application.prototype._initConnectivitySubscription = function () {
670
+ if (this.PubSub && typeof this.PubSub.subscribe === 'function') {
671
+ if (!this._unsubscribeConnectivity) {
672
+ this._unsubscribeConnectivity = this.PubSub.subscribe('Application.status!', value => {
673
+ var _this$AppStore$dispat5, _this$AppStore5;
674
+ // Prefer store so UI updates predictably
675
+ (_this$AppStore$dispat5 = (_this$AppStore5 = this.AppStore).dispatch) === null || _this$AppStore$dispat5 === void 0 || _this$AppStore$dispat5.call(_this$AppStore5, {
676
+ type: _actions.SET_CONNECTIVITY_STATUS,
677
+ status: value.status
678
+ });
679
+ });
680
+ }
681
+ return;
682
+ }
683
+ /*
684
+ This is a safety precaution which retry initialization, PubSubProvider may assigns app.PubSub
685
+ in its useEffect after first render (however we use useLayoutEffect there, so it should not be an issue).
686
+ */
687
+ setTimeout(this._initConnectivitySubscription, 0);
688
+ };
651
689
  var _default = exports.default = Application;
@@ -30,14 +30,10 @@ var SidebarItem = _ref => {
30
30
  var _state$sidebar;
31
31
  return (_state$sidebar = state.sidebar) === null || _state$sidebar === void 0 ? void 0 : _state$sidebar.isSidebarCollapsed;
32
32
  });
33
- var attentionRequired = (0, _asab_webui_components.useAppSelector)(state => {
34
- var _state$attentionrequi;
35
- return (_state$attentionrequi = state.attentionrequired) === null || _state$attentionrequi === void 0 ? void 0 : _state$attentionrequi.beacon;
36
- });
37
33
  var [isActive, setActive] = (0, _react.useState)(false);
38
34
  var lowercasedItemName = item === null || item === void 0 || (_item$name = item.name) === null || _item$name === void 0 ? void 0 : _item$name.toLowerCase();
39
35
  // Attention requred flag
40
- var itemBeacon = beacon ? beacon["beacon.".concat(lowercasedItemName)] : attentionRequired === null || attentionRequired === void 0 ? void 0 : attentionRequired["beacon.".concat(lowercasedItemName)];
36
+ var itemBeacon = beacon ? beacon["beacon.".concat(lowercasedItemName)] : {};
41
37
  (0, _react.useEffect)(() => {
42
38
  // TODO: refactor the handling of active and open states, since it does not behave as expected in some cases
43
39
  if (isOpen && !isActive) {
@@ -80,7 +76,8 @@ var SidebarCollapsibleItem = _ref2 => {
80
76
  var {
81
77
  item,
82
78
  disabled,
83
- isSmallResolution
79
+ isSmallResolution,
80
+ beacon = undefined
84
81
  } = _ref2;
85
82
  var [isOpen, setOpen] = (0, _react.useState)(false);
86
83
  var location = (0, _reactRouter.useLocation)();
@@ -91,14 +88,10 @@ var SidebarCollapsibleItem = _ref2 => {
91
88
  var _state$sidebar2;
92
89
  return (_state$sidebar2 = state.sidebar) === null || _state$sidebar2 === void 0 ? void 0 : _state$sidebar2.isSidebarCollapsed;
93
90
  });
94
- var attentionRequired = (0, _asab_webui_components.useAppSelector)(state => {
95
- var _state$attentionrequi2;
96
- return (_state$attentionrequi2 = state.attentionrequired) === null || _state$attentionrequi2 === void 0 ? void 0 : _state$attentionrequi2.beacon;
97
- });
98
91
  var lowercasedItemName = item.name.toLowerCase();
99
92
 
100
93
  // Attention required flag
101
- var itemsBeacon = parentBeacon(attentionRequired, "beacon.".concat(lowercasedItemName), lowercasedItemName);
94
+ var itemsBeacon = parentBeacon(beacon, "beacon.".concat(lowercasedItemName), lowercasedItemName);
102
95
  // Summarize parent beacon count based on children
103
96
  var beaconCount = itemsBeacon && Object.keys(itemsBeacon).length > 0 ? Object.values(itemsBeacon).reduce((sum, current) => {
104
97
  return sum + (current.count || 0); // Ensure we sum only valid count values
@@ -11,7 +11,8 @@ var SidebarItemRenderer = _ref => {
11
11
  var {
12
12
  sidebarItems,
13
13
  sessionExpired,
14
- isSmallResolution = undefined
14
+ isSmallResolution = undefined,
15
+ beacon = undefined
15
16
  } = _ref;
16
17
  return /*#__PURE__*/_react.default.createElement("nav", {
17
18
  className: "nav flex-column"
@@ -19,12 +20,14 @@ var SidebarItemRenderer = _ref => {
19
20
  key: idx,
20
21
  item: item,
21
22
  disabled: sessionExpired,
22
- isSmallResolution: isSmallResolution
23
+ isSmallResolution: isSmallResolution,
24
+ beacon: beacon
23
25
  }) : /*#__PURE__*/_react.default.createElement(_SidebarItem.SidebarItem, {
24
26
  key: idx,
25
27
  item: item,
26
28
  disabled: sessionExpired,
27
- isSmallResolution: isSmallResolution
29
+ isSmallResolution: isSmallResolution,
30
+ beacon: beacon
28
31
  })));
29
32
  };
30
33
  exports.SidebarItemRenderer = SidebarItemRenderer;
@@ -53,6 +53,11 @@ function Sidebar(props) {
53
53
  return (_state$auth = state.auth) === null || _state$auth === void 0 ? void 0 : _state$auth.sessionExpired;
54
54
  });
55
55
  var theme = (0, _asab_webui_components.useAppSelector)(state => state.theme);
56
+ // Subscription to a beacon
57
+ var beacon = (0, _asab_webui_components.useAppSelector)(state => {
58
+ var _state$attentionrequi;
59
+ return state === null || state === void 0 || (_state$attentionrequi = state.attentionrequired) === null || _state$attentionrequi === void 0 ? void 0 : _state$attentionrequi.beacon;
60
+ });
56
61
  var {
57
62
  dispatch
58
63
  } = (0, _asab_webui_components.useAppStore)();
@@ -98,13 +103,15 @@ function Sidebar(props) {
98
103
  }, /*#__PURE__*/_react.default.createElement(_SidebarItemRenderer.SidebarItemRenderer, {
99
104
  sidebarItems: sidebarItems,
100
105
  sessionExpired: sessionExpired,
101
- isSmallResolution: isSmallResolution
106
+ isSmallResolution: isSmallResolution,
107
+ beacon: beacon
102
108
  })))) : /*#__PURE__*/_react.default.createElement("div", {
103
109
  className: "h-100",
104
110
  id: "app-sidebar"
105
111
  }, /*#__PURE__*/_react.default.createElement(_SidebarItemRenderer.SidebarItemRenderer, {
106
112
  sidebarItems: sidebarItems,
107
- sessionExpired: sessionExpired
113
+ sessionExpired: sessionExpired,
114
+ beacon: beacon
108
115
  }), /*#__PURE__*/_react.default.createElement("div", {
109
116
  className: "flex-fill"
110
117
  }, "\xA0"), /*#__PURE__*/_react.default.createElement(_SidebarBottomItem.default, {
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.OfflineIndication = OfflineIndication;
8
+ var _react = _interopRequireDefault(require("react"));
9
+ var _reactI18next = require("react-i18next");
10
+ // Header badge for displaying offline status
11
+ function OfflineIndication() {
12
+ var {
13
+ t
14
+ } = (0, _reactI18next.useTranslation)();
15
+ return /*#__PURE__*/_react.default.createElement("i", {
16
+ title: t('General|You are offline. Functionality of the Application may be limited.'),
17
+ className: "bi bi-wifi-off mx-3",
18
+ style: {
19
+ fontSize: '16px'
20
+ } // Align with fontsize of navitems of Header
21
+ });
22
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+
4
+ // Header badge for displaying offline status
5
+ export function OfflineIndication() {
6
+ const { t } = useTranslation();
7
+
8
+ return <i
9
+ title={t('General|You are offline. Functionality of the Application may be limited.')}
10
+ className='bi bi-wifi-off mx-3'
11
+ style={{ fontSize: '16px' }} // Align with fontsize of navitems of Header
12
+ />
13
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = connectivityReducer;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _actions = require("../../actions");
10
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
11
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
12
+ var initialState = {
13
+ status: null
14
+ };
15
+
16
+ // Reducer for connectivity status
17
+ function connectivityReducer() {
18
+ var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;
19
+ var action = arguments.length > 1 ? arguments[1] : undefined;
20
+ switch (action.type) {
21
+ case _actions.SET_CONNECTIVITY_STATUS:
22
+ {
23
+ return _objectSpread(_objectSpread({}, state), {}, {
24
+ status: action.status
25
+ });
26
+ }
27
+ default:
28
+ return state;
29
+ }
30
+ }
@@ -0,0 +1,20 @@
1
+ import { SET_CONNECTIVITY_STATUS } from "../../actions";
2
+
3
+ const initialState = {
4
+ status: null
5
+ };
6
+
7
+ // Reducer for connectivity status
8
+ export default function connectivityReducer(state = initialState, action) {
9
+ switch (action.type) {
10
+ case SET_CONNECTIVITY_STATUS: {
11
+ return {
12
+ ...state,
13
+ status: action.status
14
+ }
15
+ }
16
+
17
+ default:
18
+ return state;
19
+ }
20
+ }
@@ -8,14 +8,16 @@ exports.default = void 0;
8
8
  var _react = _interopRequireDefault(require("react"));
9
9
  var _asab_webui_components = require("asab_webui_components");
10
10
  var _service = _interopRequireDefault(require("./service.js"));
11
- var _reducer = _interopRequireDefault(require("./reducer.js"));
11
+ var _connectivityReducer = _interopRequireDefault(require("./connectivityReducer.js"));
12
+ var _attentionRequiredReducer = _interopRequireDefault(require("./attentionRequiredReducer.js"));
12
13
  class AttentionRequiredModule extends _asab_webui_components.Module {
13
14
  constructor(app) {
14
15
  var name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "AttentionRequiredModule";
15
16
  super(app, name);
16
17
  this.App = app;
17
18
  this.AttentionRequiredService = new _service.default(app, "AttentionRequiredService");
18
- this.App.ReduxService.addReducer("attentionrequired", _reducer.default);
19
+ this.App.ReduxService.addReducer("attentionrequired", _attentionRequiredReducer.default);
20
+ this.App.ReduxService.addReducer("connectivity", _connectivityReducer.default);
19
21
  }
20
22
  }
21
23
  exports.default = AttentionRequiredModule;
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
  import { Module } from 'asab_webui_components';
3
3
  import AttentionRequiredService from './service.jsx';
4
- import reducer from './reducer.jsx';
4
+ import connectivityReducer from './connectivityReducer.jsx';
5
+ import attentionRequiredReducer from './attentionRequiredReducer.jsx';
5
6
 
6
7
  export default class AttentionRequiredModule extends Module {
7
8
 
@@ -11,7 +12,8 @@ export default class AttentionRequiredModule extends Module {
11
12
 
12
13
  this.AttentionRequiredService = new AttentionRequiredService(app, "AttentionRequiredService");
13
14
 
14
- this.App.ReduxService.addReducer("attentionrequired", reducer);
15
+ this.App.ReduxService.addReducer("attentionrequired", attentionRequiredReducer);
16
+ this.App.ReduxService.addReducer("connectivity", connectivityReducer);
15
17
  }
16
18
 
17
19
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _asab_webui_components = require("asab_webui_components");
8
8
  var _actions = require("../../actions");
9
+ var _OfflineIndication = require("./components/OfflineIndication.js");
9
10
  // Service handling attention required
10
11
  class AttentionRequiredService extends _asab_webui_components.Service {
11
12
  constructor(app) {
@@ -14,6 +15,8 @@ class AttentionRequiredService extends _asab_webui_components.Service {
14
15
  this.beaconWebSocket = null; // beacon websocket variable
15
16
  this.reconnectTimeout = null; // timeout variable
16
17
  this.reconnectInterval = 5000; // initial reconnection interval (5 seconds)
18
+ this.prevStatus = null; // initial status
19
+ this.prevBeacon = null; // initial beacon
17
20
  }
18
21
 
19
22
  // Start beacon websocket connection on initialization
@@ -28,6 +31,7 @@ class AttentionRequiredService extends _asab_webui_components.Service {
28
31
  var currentTenant = (_this$App = this.App) === null || _this$App === void 0 || (_this$App = _this$App.Services) === null || _this$App === void 0 || (_this$App = _this$App.TenantService) === null || _this$App === void 0 ? void 0 : _this$App.getCurrentTenant();
29
32
  // Gracefully reconnect if tenant was not found
30
33
  if (!currentTenant) {
34
+ // TODO: maybe set offline
31
35
  console.error("Beacon WebSocket error: Tenant was not found!");
32
36
  this.reconnectBeaconWebSocket();
33
37
  return;
@@ -35,7 +39,7 @@ class AttentionRequiredService extends _asab_webui_components.Service {
35
39
  var BeaconServiceClient = this.App.createWebSocket('asab-remote-control', "/beacon/".concat(currentTenant, "/ws"));
36
40
  this.beaconWebSocket = BeaconServiceClient;
37
41
  this.beaconWebSocket.onopen = () => {
38
- // TODO: may be removed
42
+ this.connectionStatus('online');
39
43
  console.log('Beacon WebSocket connection established.');
40
44
  };
41
45
  this.beaconWebSocket.onmessage = message => {
@@ -51,10 +55,12 @@ class AttentionRequiredService extends _asab_webui_components.Service {
51
55
  this.beaconWebSocket.onerror = error => {
52
56
  console.error("Beacon WebSocket error:", error);
53
57
  console.error("Beacon WebSocket is attempting to reconnect...");
58
+ this.connectionStatus('offline');
54
59
  this.distributeData({});
55
60
  this.reconnectBeaconWebSocket();
56
61
  };
57
62
  this.beaconWebSocket.onclose = () => {
63
+ this.connectionStatus('offline');
58
64
  /*
59
65
  WebSocket connection is closed by the browser automatically when user
60
66
  leaves the application (not the screen).
@@ -99,13 +105,24 @@ class AttentionRequiredService extends _asab_webui_components.Service {
99
105
 
100
106
  // Method for distributing the data into Application store
101
107
  distributeData(data) {
102
- var _this$App2, _this$App2$dispatch;
103
- var transformedData = this.transformData(data);
104
- // TODO: use a pubsub instead of the appstore dispatch
105
- (_this$App2 = this.App) === null || _this$App2 === void 0 || (_this$App2 = _this$App2.AppStore) === null || _this$App2 === void 0 || (_this$App2$dispatch = _this$App2.dispatch) === null || _this$App2$dispatch === void 0 || _this$App2$dispatch.call(_this$App2, {
106
- type: _actions.SET_ATTENTION_REQUIRED_BEACON,
107
- beacon: transformedData
108
- });
108
+ var next = this.transformData(data);
109
+ var initial = this.prevBeacon === null;
110
+ var nextEmpty = _isEmptyObject(next);
111
+ var prevEmpty = _isEmptyObject(this.prevBeacon);
112
+
113
+ /*
114
+ We dont want to repetitivelly dispatch empty beacons to the AppStore
115
+ because it is causing unwanted re-rendering of the Application
116
+ */
117
+ if (initial || !nextEmpty || nextEmpty && !prevEmpty) {
118
+ var _this$App2, _this$App2$dispatch;
119
+ (_this$App2 = this.App) === null || _this$App2 === void 0 || (_this$App2 = _this$App2.AppStore) === null || _this$App2 === void 0 || (_this$App2$dispatch = _this$App2.dispatch) === null || _this$App2$dispatch === void 0 || _this$App2$dispatch.call(_this$App2, {
120
+ type: _actions.SET_ATTENTION_REQUIRED_BEACON,
121
+ beacon: next
122
+ });
123
+ // Update previous snapshot after dispatch
124
+ this.prevBeacon = next;
125
+ }
109
126
  }
110
127
 
111
128
  // Transform and group beacon data
@@ -142,5 +159,35 @@ class AttentionRequiredService extends _asab_webui_components.Service {
142
159
  return acc;
143
160
  }, {});
144
161
  }
162
+ // Method for publishing the connection status and displaying offline indication
163
+ connectionStatus(status) {
164
+ var _this$App3;
165
+ if ((_this$App3 = this.App) !== null && _this$App3 !== void 0 && _this$App3.PubSub) {
166
+ var _this$App4, _this$App5;
167
+ // Distribute status
168
+ (_this$App4 = this.App) === null || _this$App4 === void 0 || (_this$App4 = _this$App4.PubSub) === null || _this$App4 === void 0 || _this$App4.publish('Application.status!', {
169
+ status
170
+ });
171
+ var headerService = (_this$App5 = this.App) === null || _this$App5 === void 0 || (_this$App5 = _this$App5.Services) === null || _this$App5 === void 0 ? void 0 : _this$App5.HeaderService;
172
+ if (headerService) {
173
+ if (this.prevStatus != status) {
174
+ if (status === 'offline') {
175
+ headerService === null || headerService === void 0 || headerService.addComponent({
176
+ component: _OfflineIndication.OfflineIndication,
177
+ order: 100
178
+ });
179
+ } else {
180
+ headerService === null || headerService === void 0 || headerService.removeComponent(_OfflineIndication.OfflineIndication);
181
+ }
182
+ this.prevStatus = status;
183
+ }
184
+ }
185
+ }
186
+ }
145
187
  }
146
- exports.default = AttentionRequiredService;
188
+
189
+ // Method for identifying an empty object
190
+ exports.default = AttentionRequiredService;
191
+ function _isEmptyObject(o) {
192
+ return o != null && typeof o === 'object' && Object.keys(o).length === 0;
193
+ }
@@ -1,5 +1,6 @@
1
1
  import { Service } from 'asab_webui_components';
2
2
  import { SET_ATTENTION_REQUIRED_BEACON } from '../../actions';
3
+ import { OfflineIndication } from './components/OfflineIndication.jsx';
3
4
 
4
5
  // Service handling attention required
5
6
  export default class AttentionRequiredService extends Service {
@@ -9,6 +10,8 @@ export default class AttentionRequiredService extends Service {
9
10
  this.beaconWebSocket = null; // beacon websocket variable
10
11
  this.reconnectTimeout = null; // timeout variable
11
12
  this.reconnectInterval = 5000; // initial reconnection interval (5 seconds)
13
+ this.prevStatus = null; // initial status
14
+ this.prevBeacon = null; // initial beacon
12
15
  }
13
16
 
14
17
  // Start beacon websocket connection on initialization
@@ -22,6 +25,7 @@ export default class AttentionRequiredService extends Service {
22
25
  const currentTenant = this.App?.Services?.TenantService?.getCurrentTenant();
23
26
  // Gracefully reconnect if tenant was not found
24
27
  if (!currentTenant) {
28
+ // TODO: maybe set offline
25
29
  console.error("Beacon WebSocket error: Tenant was not found!");
26
30
  this.reconnectBeaconWebSocket();
27
31
  return;
@@ -31,7 +35,7 @@ export default class AttentionRequiredService extends Service {
31
35
  this.beaconWebSocket = BeaconServiceClient;
32
36
 
33
37
  this.beaconWebSocket.onopen = () => {
34
- // TODO: may be removed
38
+ this.connectionStatus('online');
35
39
  console.log('Beacon WebSocket connection established.');
36
40
  };
37
41
 
@@ -49,11 +53,13 @@ export default class AttentionRequiredService extends Service {
49
53
  this.beaconWebSocket.onerror = (error) => {
50
54
  console.error("Beacon WebSocket error:", error);
51
55
  console.error("Beacon WebSocket is attempting to reconnect...");
56
+ this.connectionStatus('offline');
52
57
  this.distributeData({});
53
58
  this.reconnectBeaconWebSocket();
54
59
  };
55
60
 
56
61
  this.beaconWebSocket.onclose = () => {
62
+ this.connectionStatus('offline');
57
63
  /*
58
64
  WebSocket connection is closed by the browser automatically when user
59
65
  leaves the application (not the screen).
@@ -99,9 +105,23 @@ export default class AttentionRequiredService extends Service {
99
105
 
100
106
  // Method for distributing the data into Application store
101
107
  distributeData(data) {
102
- const transformedData = this.transformData(data);
103
- // TODO: use a pubsub instead of the appstore dispatch
104
- this.App?.AppStore?.dispatch?.({ type: SET_ATTENTION_REQUIRED_BEACON, beacon: transformedData });
108
+ const next = this.transformData(data);
109
+ const initial = (this.prevBeacon === null);
110
+ const nextEmpty = _isEmptyObject(next);
111
+ const prevEmpty = _isEmptyObject(this.prevBeacon);
112
+
113
+ /*
114
+ We dont want to repetitivelly dispatch empty beacons to the AppStore
115
+ because it is causing unwanted re-rendering of the Application
116
+ */
117
+ if (initial || !nextEmpty || (nextEmpty && !prevEmpty)) {
118
+ this.App?.AppStore?.dispatch?.({
119
+ type: SET_ATTENTION_REQUIRED_BEACON,
120
+ beacon: next,
121
+ });
122
+ // Update previous snapshot after dispatch
123
+ this.prevBeacon = next;
124
+ }
105
125
  }
106
126
 
107
127
  // Transform and group beacon data
@@ -138,4 +158,32 @@ export default class AttentionRequiredService extends Service {
138
158
  return acc;
139
159
  }, {});
140
160
  };
161
+
162
+ // Method for publishing the connection status and displaying offline indication
163
+ connectionStatus(status) {
164
+ if (this.App?.PubSub) {
165
+ // Distribute status
166
+ this.App?.PubSub?.publish('Application.status!', { status });
167
+ const headerService = this.App?.Services?.HeaderService;
168
+ if (headerService) {
169
+ if (this.prevStatus != status) {
170
+ if (status === 'offline') {
171
+ headerService?.addComponent({
172
+ component: OfflineIndication,
173
+ order: 100
174
+ });
175
+ } else {
176
+ headerService?.removeComponent(OfflineIndication);
177
+ }
178
+ this.prevStatus = status;
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ }
185
+
186
+ // Method for identifying an empty object
187
+ function _isEmptyObject(o) {
188
+ return o != null && typeof o === 'object' && Object.keys(o).length === 0;
141
189
  }
package/package.json CHANGED
@@ -1,56 +1,56 @@
1
1
  {
2
- "name": "asab_webui_shell",
3
- "version": "27.1.5",
4
- "license": "BSD-3-Clause",
5
- "description": "TeskaLabs ASAB WebUI Shell Application",
6
- "contributors": [
7
- {
8
- "name": "TeskaLabs",
9
- "email": "info@teskalabs.com",
10
- "url": "https://teskalabs.com"
11
- }
12
- ],
13
- "repository": {
14
- "type": "git",
15
- "url": "git+ssh://git@github.com:TeskaLabs/asab-webui-shell-lib.git"
16
- },
17
- "bugs": {
18
- "url": "https://github.com/TeskaLabs/asab-webui-shell-lib/issues"
19
- },
20
- "homepage": "https://github.com/TeskaLabs/asab-webui-shell-lib",
21
- "main": "dist/index.js",
22
- "files": [
23
- "dist"
24
- ],
25
- "publishConfig": {
26
- "registry": "https://registry.npmjs.org/"
27
- },
28
- "dependencies": {
29
- "i18next-browser-languagedetector": "^8.2.0"
30
- },
31
- "devDependencies": {
32
- "@babel/cli": "^7.22.11",
33
- "@babel/core": "^7.22.11",
34
- "@babel/plugin-transform-runtime": "^7.22.10",
35
- "@babel/preset-env": "^7.22.10",
36
- "@babel/preset-react": "^7.22.5",
37
- "babel-plugin-module-resolver": "^5.0.2",
38
- "copyfiles": "^2.4.1",
39
- "rimraf": "^6.0.1"
40
- },
41
- "peerDependencies": {
42
- "asab_webui_components": "^27.1.1",
43
- "@babel/runtime": "^7.22.11",
44
- "react": "^19.0.0",
45
- "react-dom": "^19.0.0",
46
- "axios": "^1.8.4",
47
- "i18next": "^25.4.2",
48
- "react-i18next": "^15.7.3",
49
- "react-router": "^7.8.2",
50
- "reactstrap": "^9.2.0"
51
- },
52
- "scripts": {
53
- "clean": "rimraf dist",
54
- "build": "babel src --extensions '.js,.jsx' --out-dir dist && copyfiles -u 1 'src/**/*.{scss,jsx}' dist"
55
- }
56
- }
2
+ "name": "asab_webui_shell",
3
+ "version": "27.2.2",
4
+ "license": "BSD-3-Clause",
5
+ "description": "TeskaLabs ASAB WebUI Shell Application",
6
+ "contributors": [
7
+ {
8
+ "name": "TeskaLabs",
9
+ "email": "info@teskalabs.com",
10
+ "url": "https://teskalabs.com"
11
+ }
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+ssh://git@github.com:TeskaLabs/asab-webui-shell-lib.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/TeskaLabs/asab-webui-shell-lib/issues"
19
+ },
20
+ "homepage": "https://github.com/TeskaLabs/asab-webui-shell-lib",
21
+ "main": "dist/index.js",
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "publishConfig": {
26
+ "registry": "https://registry.npmjs.org/"
27
+ },
28
+ "scripts": {
29
+ "clean": "rimraf dist",
30
+ "build": "babel src --extensions '.js,.jsx' --out-dir dist && copyfiles -u 1 'src/**/*.{scss,jsx}' dist"
31
+ },
32
+ "dependencies": {
33
+ "i18next-browser-languagedetector": "^8.2.0"
34
+ },
35
+ "devDependencies": {
36
+ "@babel/cli": "^7.22.11",
37
+ "@babel/core": "^7.22.11",
38
+ "@babel/plugin-transform-runtime": "^7.22.10",
39
+ "@babel/preset-env": "^7.22.10",
40
+ "@babel/preset-react": "^7.22.5",
41
+ "babel-plugin-module-resolver": "^5.0.2",
42
+ "copyfiles": "^2.4.1",
43
+ "rimraf": "^6.0.1"
44
+ },
45
+ "peerDependencies": {
46
+ "asab_webui_components": "^27.3.1",
47
+ "@babel/runtime": "^7.22.11",
48
+ "react": "^19.0.0",
49
+ "react-dom": "^19.0.0",
50
+ "axios": "^1.8.4",
51
+ "i18next": "^25.5.2",
52
+ "react-i18next": "^16.0.0",
53
+ "react-router": "^7.8.2",
54
+ "reactstrap": "^9.2.0"
55
+ }
56
+ }