@fleetbase/ember-core 0.2.16 → 0.2.18

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.
@@ -7,8 +7,9 @@ import { isBlank } from '@ember/utils';
7
7
  import { A, isArray } from '@ember/array';
8
8
  import { later } from '@ember/runloop';
9
9
  import { dasherize, camelize } from '@ember/string';
10
+ import { pluralize } from 'ember-inflector';
10
11
  import { getOwner } from '@ember/application';
11
- import { assert, debug } from '@ember/debug';
12
+ import { assert, debug, warn } from '@ember/debug';
12
13
  import RSVP from 'rsvp';
13
14
  import loadInstalledExtensions from '../utils/load-installed-extensions';
14
15
  import loadExtensions from '../utils/load-extensions';
@@ -18,18 +19,21 @@ import config from 'ember-get-config';
18
19
  export default class UniverseService extends Service.extend(Evented) {
19
20
  @service router;
20
21
  @service intl;
22
+ @tracked applicationInstance;
23
+ @tracked enginesBooted = false;
24
+ @tracked bootedExtensions = A([]);
21
25
  @tracked headerMenuItems = A([]);
22
26
  @tracked organizationMenuItems = A([]);
23
27
  @tracked userMenuItems = A([]);
24
- @tracked adminRegistry = {
28
+ @tracked consoleAdminRegistry = {
25
29
  menuItems: A([]),
26
30
  menuPanels: A([]),
27
31
  };
28
- @tracked accountRegistry = {
32
+ @tracked consoleAccountRegistry = {
29
33
  menuItems: A([]),
30
34
  menuPanels: A([]),
31
35
  };
32
- @tracked settingsRegistry = {
36
+ @tracked consoleSettingsRegistry = {
33
37
  menuItems: A([]),
34
38
  menuPanels: A([]),
35
39
  };
@@ -37,6 +41,7 @@ export default class UniverseService extends Service.extend(Evented) {
37
41
  defaultWidgets: A([]),
38
42
  widgets: A([]),
39
43
  };
44
+ @tracked hooks = {};
40
45
 
41
46
  /**
42
47
  * Computed property that returns all administrative menu items.
@@ -47,8 +52,8 @@ export default class UniverseService extends Service.extend(Evented) {
47
52
  * @memberof UniverseService
48
53
  * @returns {Array} Array of administrative menu items
49
54
  */
50
- @computed('adminRegistry.menuItems.[]') get adminMenuItems() {
51
- return this.adminRegistry.menuItems;
55
+ @computed('consoleAdminRegistry.menuItems.[]') get adminMenuItems() {
56
+ return this.consoleAdminRegistry.menuItems;
52
57
  }
53
58
 
54
59
  /**
@@ -60,8 +65,8 @@ export default class UniverseService extends Service.extend(Evented) {
60
65
  * @memberof UniverseService
61
66
  * @returns {Array} Array of administrative menu panels
62
67
  */
63
- @computed('adminRegistry.menuPanels.[]') get adminMenuPanels() {
64
- return this.adminRegistry.menuPanels;
68
+ @computed('consoleAdminRegistry.menuPanels.[]') get adminMenuPanels() {
69
+ return this.consoleAdminRegistry.menuPanels;
65
70
  }
66
71
 
67
72
  /**
@@ -73,8 +78,8 @@ export default class UniverseService extends Service.extend(Evented) {
73
78
  * @memberof UniverseService
74
79
  * @returns {Array} Array of administrative menu items
75
80
  */
76
- @computed('settingsRegistry.menuItems.[]') get settingsMenuItems() {
77
- return this.settingsRegistry.menuItems;
81
+ @computed('consoleSettingsRegistry.menuItems.[]') get settingsMenuItems() {
82
+ return this.consoleSettingsRegistry.menuItems;
78
83
  }
79
84
 
80
85
  /**
@@ -86,8 +91,8 @@ export default class UniverseService extends Service.extend(Evented) {
86
91
  * @memberof UniverseService
87
92
  * @returns {Array} Array of administrative menu panels
88
93
  */
89
- @computed('settingsRegistry.menuPanels.[]') get settingsMenuPanels() {
90
- return this.settingsRegistry.menuPanels;
94
+ @computed('consoleSettingsRegistry.menuPanels.[]') get settingsMenuPanels() {
95
+ return this.consoleSettingsRegistry.menuPanels;
91
96
  }
92
97
 
93
98
  /**
@@ -130,6 +135,15 @@ export default class UniverseService extends Service.extend(Evented) {
130
135
  return this.router.transitionTo(route, ...args);
131
136
  }
132
137
 
138
+ /**
139
+ * Retrieves the application instance.
140
+ *
141
+ * @returns {ApplicationInstance} - The application instance object.
142
+ */
143
+ getApplicationInstance() {
144
+ return this.applicationInstance;
145
+ }
146
+
133
147
  /**
134
148
  * Retrieves the mount point of a specified engine by its name.
135
149
 
@@ -230,13 +244,21 @@ export default class UniverseService extends Service.extend(Evented) {
230
244
  * @returns {Transition} Returns a Transition object representing the transition to the route.
231
245
  */
232
246
  @action transitionMenuItem(route, menuItem) {
233
- const { slug, view } = menuItem;
247
+ const { slug, view, section } = menuItem;
248
+
249
+ if (section && slug && view) {
250
+ return this.router.transitionTo(route, section, slug, { queryParams: { view } });
251
+ }
234
252
 
235
- if (view) {
236
- return this.router.transitionTo(route, slug, view);
253
+ if (section && slug) {
254
+ return this.router.transitionTo(route, section, slug);
237
255
  }
238
256
 
239
- return this.router.transitionTo(route, slug, 'index');
257
+ if (slug && view) {
258
+ return this.router.transitionTo(route, slug, { queryParams: { view } });
259
+ }
260
+
261
+ return this.router.transitionTo(route, slug);
240
262
  }
241
263
 
242
264
  /**
@@ -259,13 +281,20 @@ export default class UniverseService extends Service.extend(Evented) {
259
281
  @action createRegistry(registryName, options = {}) {
260
282
  const internalRegistryName = this.createInternalRegistryName(registryName);
261
283
 
262
- this[internalRegistryName] = {
263
- name: registryName,
264
- menuItems: [],
265
- menuPanels: [],
266
- renderableComponents: [],
267
- ...options,
268
- };
284
+ if (this[internalRegistryName] == undefined) {
285
+ this[internalRegistryName] = {
286
+ name: registryName,
287
+ menuItems: [],
288
+ menuPanels: [],
289
+ renderableComponents: [],
290
+ ...options,
291
+ };
292
+ } else {
293
+ this[internalRegistryName] = {
294
+ ...this[internalRegistryName],
295
+ ...options,
296
+ };
297
+ }
269
298
 
270
299
  // trigger registry created event
271
300
  this.trigger('registry.created', this[internalRegistryName]);
@@ -519,10 +548,11 @@ export default class UniverseService extends Service.extend(Evented) {
519
548
  * @param {string} registryName - The name of the registry where the menu item is located.
520
549
  * @param {string} slug - The slug of the menu item.
521
550
  * @param {string} [view=null] - The view of the menu item, if applicable.
551
+ * @param {string} [section=null] - The section of the menu item, if applicable.
522
552
  *
523
553
  * @returns {Promise} Returns a Promise that resolves with the menu item if it is found, or null.
524
554
  */
525
- lookupMenuItemFromRegistry(registryName, slug, view = null) {
555
+ lookupMenuItemFromRegistry(registryName, slug, view = null, section = null) {
526
556
  const internalRegistryName = this.createInternalRegistryName(registryName);
527
557
  const registry = this[internalRegistryName];
528
558
 
@@ -537,8 +567,7 @@ export default class UniverseService extends Service.extend(Evented) {
537
567
  for (let i = 0; i < registry.menuItems.length; i++) {
538
568
  const menuItem = registry.menuItems[i];
539
569
 
540
- // no view hack
541
- if (menuItem && menuItem.slug === slug && menuItem.view === null && view === 'index') {
570
+ if (menuItem && menuItem.slug === slug && menuItem.section === section && menuItem.view === view) {
542
571
  foundMenuItem = menuItem;
543
572
  break;
544
573
  }
@@ -557,8 +586,7 @@ export default class UniverseService extends Service.extend(Evented) {
557
586
  for (let j = 0; j < menuPanel.items.length; j++) {
558
587
  const menuItem = menuPanel.items[j];
559
588
 
560
- // no view hack
561
- if (menuItem && menuItem.slug === slug && menuItem.view === null && view === 'index') {
589
+ if (menuItem && menuItem.slug === slug && menuItem.section === section && menuItem.view === view) {
562
590
  foundMenuItem = menuItem;
563
591
  break;
564
592
  }
@@ -575,6 +603,134 @@ export default class UniverseService extends Service.extend(Evented) {
575
603
  });
576
604
  }
577
605
 
606
+ /**
607
+ * Gets the view param from the transition object.
608
+ *
609
+ * @param {Transition} transition
610
+ * @return {String|Null}
611
+ * @memberof UniverseService
612
+ */
613
+ getViewFromTransition(transition) {
614
+ const queryParams = transition.to.queryParams ?? { view: null };
615
+ return queryParams.view;
616
+ }
617
+
618
+ /**
619
+ * Creates an internal registry name for hooks based on a given registry name.
620
+ * The registry name is transformed to camel case and appended with 'Hooks'.
621
+ * Non-alphanumeric characters are replaced with hyphens.
622
+ *
623
+ * @param {string} registryName - The name of the registry for which to create an internal hook registry name.
624
+ * @returns {string} - The internal hook registry name, formatted as camel case with 'Hooks' appended.
625
+ */
626
+ createInternalHookRegistryName(registryName) {
627
+ return `${camelize(registryName.replace(/[^a-zA-Z0-9]/g, '-'))}Hooks`;
628
+ }
629
+
630
+ /**
631
+ * Registers a hook function under a specified registry name.
632
+ * The hook is stored in an internal registry, and its hash is computed for identification.
633
+ * If the hook is already registered, it is appended to the existing list of hooks.
634
+ *
635
+ * @param {string} registryName - The name of the registry where the hook should be registered.
636
+ * @param {Function} hook - The hook function to be registered.
637
+ */
638
+ registerHook(registryName, hook) {
639
+ if (typeof hook !== 'function') {
640
+ throw new Error('The hook must be a function.');
641
+ }
642
+
643
+ // no duplicate hooks
644
+ if (this.didRegisterHook(registryName, hook)) {
645
+ return;
646
+ }
647
+
648
+ const internalHookRegistryName = this.createInternalHookRegistryName(registryName);
649
+ const hookRegistry = this.hooks[internalHookRegistryName] || [];
650
+ hookRegistry.pushObject({ id: this._createHashFromFunctionDefinition(hook), hook });
651
+
652
+ this.hooks[internalHookRegistryName] = hookRegistry;
653
+ }
654
+
655
+ /**
656
+ * Checks if a hook was registered already.
657
+ *
658
+ * @param {String} registryName
659
+ * @param {Function} hook
660
+ * @return {Boolean}
661
+ * @memberof UniverseService
662
+ */
663
+ didRegisterHook(registryName, hook) {
664
+ const hooks = this.getHooks(registryName);
665
+ const hookId = this._createHashFromFunctionDefinition(hook);
666
+ return isArray(hooks) && hooks.some((h) => h.id === hookId);
667
+ }
668
+
669
+ /**
670
+ * Retrieves the list of hooks registered under a specified registry name.
671
+ * If no hooks are registered, returns an empty array.
672
+ *
673
+ * @param {string} registryName - The name of the registry for which to retrieve hooks.
674
+ * @returns {Array<Object>} - An array of hook objects registered under the specified registry name.
675
+ * Each object contains an `id` and a `hook` function.
676
+ */
677
+ getHooks(registryName) {
678
+ const internalHookRegistryName = this.createInternalHookRegistryName(registryName);
679
+ return this.hooks[internalHookRegistryName] ?? [];
680
+ }
681
+
682
+ /**
683
+ * Executes all hooks registered under a specified registry name with the given parameters.
684
+ * Each hook is called with the provided parameters.
685
+ *
686
+ * @param {string} registryName - The name of the registry under which hooks should be executed.
687
+ * @param {...*} params - The parameters to pass to each hook function.
688
+ */
689
+ executeHooks(registryName, ...params) {
690
+ const hooks = this.getHooks(registryName);
691
+ hooks.forEach(({ hook }) => {
692
+ try {
693
+ hook(...params);
694
+ } catch (error) {
695
+ debug(`Error executing hook: ${error}`);
696
+ }
697
+ });
698
+ }
699
+
700
+ /**
701
+ * Calls all hooks registered under a specified registry name with the given parameters.
702
+ * This is an alias for `executeHooks` for consistency in naming.
703
+ *
704
+ * @param {string} registryName - The name of the registry under which hooks should be called.
705
+ * @param {...*} params - The parameters to pass to each hook function.
706
+ */
707
+ callHooks(registryName, ...params) {
708
+ this.executeHooks(registryName, ...params);
709
+ }
710
+
711
+ /**
712
+ * Calls a specific hook identified by its ID under a specified registry name with the given parameters.
713
+ * Only the hook with the matching ID is executed.
714
+ *
715
+ * @param {string} registryName - The name of the registry where the hook is registered.
716
+ * @param {string} hookId - The unique identifier of the hook to be called.
717
+ * @param {...*} params - The parameters to pass to the hook function.
718
+ */
719
+ callHook(registryName, hookId, ...params) {
720
+ const hooks = this.getHooks(registryName);
721
+ const hook = hooks.find((h) => h.id === hookId);
722
+
723
+ if (hook) {
724
+ try {
725
+ hook.hook(...params);
726
+ } catch (error) {
727
+ debug(`Error executing hook: ${error}`);
728
+ }
729
+ } else {
730
+ warn(`Hook with ID ${hookId} not found.`);
731
+ }
732
+ }
733
+
578
734
  /**
579
735
  * Registers a renderable component or an array of components into a specified registry.
580
736
  * If a single component is provided, it is registered directly.
@@ -684,6 +840,26 @@ export default class UniverseService extends Service.extend(Evented) {
684
840
  this.trigger('menuItem.registered', menuItem, this[internalRegistryName]);
685
841
  }
686
842
 
843
+ /**
844
+ * Register multiple menu items to a registry.
845
+ *
846
+ * @param {String} registryName
847
+ * @param {Array} [menuItems=[]]
848
+ * @memberof UniverseService
849
+ */
850
+ registerMenuItems(registryName, menuItems = []) {
851
+ for (let i = 0; i < menuItems.length; i++) {
852
+ const menuItem = menuItems[i];
853
+ if (menuItem && menuItem.title) {
854
+ if (menuItem.options) {
855
+ this.registerMenuItem(registryName, menuItem.title, menuItem.options);
856
+ } else {
857
+ this.registerMenuItem(registryName, menuItem.title, menuItem);
858
+ }
859
+ }
860
+ }
861
+ }
862
+
687
863
  /**
688
864
  * Registers a menu item's component to one or multiple engines.
689
865
  *
@@ -724,7 +900,7 @@ export default class UniverseService extends Service.extend(Evented) {
724
900
  */
725
901
  registerAdminMenuPanel(title, items = [], options = {}) {
726
902
  options.section = this._getOption(options, 'section', 'admin');
727
- this.registerMenuPanel('admin', title, items, options);
903
+ this.registerMenuPanel('console:admin', title, items, options);
728
904
  }
729
905
 
730
906
  /**
@@ -737,7 +913,7 @@ export default class UniverseService extends Service.extend(Evented) {
737
913
  * @param {Object} options Additional options for the item
738
914
  */
739
915
  registerAdminMenuItem(title, options = {}) {
740
- this.registerMenuItem('admin', title, options);
916
+ this.registerMenuItem('console:admin', title, options);
741
917
  }
742
918
 
743
919
  /**
@@ -751,94 +927,252 @@ export default class UniverseService extends Service.extend(Evented) {
751
927
  * @param {Object} options Additional options for the panel
752
928
  */
753
929
  registerSettingsMenuPanel(title, items = [], options = {}) {
754
- this.registerMenuPanel('settings', title, items, options);
930
+ this.registerMenuPanel('console:settings', title, items, options);
755
931
  }
756
932
 
757
933
  /**
758
- * Registers a new dashboard widget in the universe service.
934
+ * Registers a new settings menu item.
759
935
  *
760
- * @method registerDashboardWidgets
936
+ * @method registerSettingsMenuItem
761
937
  * @public
762
938
  * @memberof UniverseService
763
- * @param {Object} widget - The widget object containing name, component, gridOptions, and options.
764
- * @property {String} name - The name of the widget.
765
- * @property {String} icon - The iron of the widget.
766
- * @property {Function} component - The component associated with the widget.
767
- * @property {Object} gridOptions - The grid options for the widget.
768
- * @property {Object} options - Additional options for the widget.
769
- */
770
- registerDashboardWidgets(widget) {
771
- if (isArray(widget)) {
772
- widget.forEach((w) => this.registerDashboardWidgets(w));
773
- return;
774
- }
775
-
776
- const newWidget = this._createDashboardWidget(widget);
777
- this.dashboardWidgets.widgets.pushObject(newWidget);
778
- this.trigger('widget.registered', newWidget);
939
+ * @param {String} title The title of the item
940
+ * @param {Object} options Additional options for the item
941
+ */
942
+ registerSettingsMenuItem(title, options = {}) {
943
+ this.registerMenuItem('console:settings', title, options);
779
944
  }
780
945
 
781
946
  /**
782
- * Retrieves the widgets registered in the universe service.
947
+ * Registers a new account menu panel.
783
948
  *
784
- * @method getDashboardWidgets
949
+ * @method registerAccountMenuPanel
785
950
  * @public
786
951
  * @memberof UniverseService
787
- * @returns {Array} An array of registered widgets
952
+ * @param {String} title The title of the panel
953
+ * @param {Array} items The items of the panel
954
+ * @param {Object} options Additional options for the panel
788
955
  */
789
- getDashboardWidgets() {
790
- return this.dashboardWidgets.widgets;
956
+ registerAccountMenuPanel(title, items = [], options = {}) {
957
+ this.registerMenuPanel('console:account', title, items, options);
791
958
  }
792
959
 
793
960
  /**
794
- * Registers a new dashboard widget in the universe service.
961
+ * Registers a new account menu item.
795
962
  *
796
- * @method registerDefaultDashboardWidgets
963
+ * @method registerAccountMenuItem
797
964
  * @public
798
965
  * @memberof UniverseService
799
- * @param {Object} widget - The widget object containing name, component, gridOptions, and options.
800
- * @property {String} name - The name of the widget.
801
- * @property {String} icon - The iron of the widget.
802
- * @property {Function} component - The component associated with the widget.
803
- * @property {Object} gridOptions - The grid options for the widget.
804
- * @property {Object} options - Additional options for the widget.
805
- */
806
- registerDefaultDashboardWidgets(widget) {
807
- if (isArray(widget)) {
808
- widget.forEach((w) => this.registerDefaultDashboardWidgets(w));
966
+ * @param {String} title The title of the item
967
+ * @param {Object} options Additional options for the item
968
+ */
969
+ registerAccountMenuItem(title, options = {}) {
970
+ this.registerMenuItem('console:account', title, options);
971
+ }
972
+
973
+ /**
974
+ * Registers a new dashboard with the given name.
975
+ * Initializes the dashboard with empty arrays for default widgets and widgets.
976
+ *
977
+ * @param {string} dashboardName - The name of the dashboard to register.
978
+ * @returns {void}
979
+ */
980
+ registerDashboard(dashboardName) {
981
+ const internalDashboardRegistryName = this.createInternalDashboardName(dashboardName);
982
+ if (this[internalDashboardRegistryName] !== undefined) {
809
983
  return;
810
984
  }
811
985
 
812
- const newWidget = this._createDashboardWidget(widget);
813
- this.dashboardWidgets.defaultWidgets.pushObject(newWidget);
814
- this.trigger('widget.registered', newWidget);
986
+ this[internalDashboardRegistryName] = {
987
+ defaultWidgets: A([]),
988
+ widgets: A([]),
989
+ };
990
+
991
+ this.trigger('dashboard.registered', this[internalDashboardRegistryName]);
815
992
  }
816
993
 
817
994
  /**
818
- * Retrieves the widgets registered in the universe service.
995
+ * Retrieves the registry for a specific dashboard.
819
996
  *
820
- * @method getDefaultDashboardWidgets
821
- * @public
997
+ * @param {string} dashboardName - The name of the dashboard to get the registry for.
998
+ * @returns {Object} - The registry object for the specified dashboard, including default and registered widgets.
999
+ */
1000
+ getDashboardRegistry(dashboardName) {
1001
+ const internalDashboardRegistryName = this.createInternalDashboardName(dashboardName);
1002
+ return this[internalDashboardRegistryName];
1003
+ }
1004
+
1005
+ /**
1006
+ * Checks if a dashboard has been registered.
1007
+ *
1008
+ * @param {String} dashboardName
1009
+ * @return {Boolean}
822
1010
  * @memberof UniverseService
823
- * @returns {Array} An array of registered widgets
1011
+ */
1012
+ didRegisterDashboard(dashboardName) {
1013
+ const internalDashboardRegistryName = this.createInternalDashboardName(dashboardName);
1014
+ return this[internalDashboardRegistryName] !== undefined;
1015
+ }
1016
+
1017
+ /**
1018
+ * Retrieves the widget registry for a specific dashboard and type.
1019
+ *
1020
+ * @param {string} dashboardName - The name of the dashboard to get the widget registry for.
1021
+ * @param {string} [type='widgets'] - The type of widget registry to retrieve (e.g., 'widgets', 'defaultWidgets').
1022
+ * @returns {Array} - An array of widget objects for the specified dashboard and type.
1023
+ */
1024
+ getWidgetRegistry(dashboardName, type = 'widgets') {
1025
+ const internalDashboardRegistryName = this.createInternalDashboardName(dashboardName);
1026
+ const typeKey = pluralize(type);
1027
+ return isArray(this[internalDashboardRegistryName][typeKey]) ? this[internalDashboardRegistryName][typeKey] : [];
1028
+ }
1029
+
1030
+ /**
1031
+ * Registers widgets for a specific dashboard.
1032
+ * Supports registering multiple widgets and different types of widget collections.
1033
+ *
1034
+ * @param {string} dashboardName - The name of the dashboard to register widgets for.
1035
+ * @param {Array|Object} widgets - An array of widget objects or a single widget object to register.
1036
+ * @param {string} [type='widgets'] - The type of widgets to register (e.g., 'widgets', 'defaultWidgets').
1037
+ * @returns {void}
1038
+ */
1039
+ registerWidgets(dashboardName, widgets = [], type = 'widgets') {
1040
+ const internalDashboardRegistryName = this.createInternalDashboardName(dashboardName);
1041
+ if (isArray(widgets)) {
1042
+ widgets.forEach((w) => this.registerWidgets(dashboardName, w, type));
1043
+ return;
1044
+ }
1045
+
1046
+ const typeKey = pluralize(type);
1047
+ const newWidget = this._createDashboardWidget(widgets);
1048
+ const widgetRegistry = this.getWidgetRegistry(dashboardName, type);
1049
+ if (this.widgetRegistryHasWidget(widgetRegistry, newWidget)) {
1050
+ return;
1051
+ }
1052
+
1053
+ this[internalDashboardRegistryName][typeKey] = [...widgetRegistry, newWidget];
1054
+ this.trigger('widget.registered', newWidget);
1055
+ }
1056
+
1057
+ /**
1058
+ * Checks if a widget with the same ID as the pending widget is already registered in the specified dashboard and type.
1059
+ *
1060
+ * @param {string} dashboardName - The name of the dashboard to check.
1061
+ * @param {Object} widgetPendingRegistry - The widget to check for in the registry.
1062
+ * @param {string} [type='widgets'] - The type of widget registry to check (e.g., 'widgets', 'defaultWidgets').
1063
+ * @returns {boolean} - `true` if a widget with the same ID is found in the registry; otherwise, `false`.
1064
+ */
1065
+ didRegisterWidget(dashboardName, widgetPendingRegistry, type = 'widgets') {
1066
+ const widgetRegistry = this.getWidgetRegistry(dashboardName, type);
1067
+ return widgetRegistry.includes((widget) => widget.widgetId === widgetPendingRegistry.widgetId);
1068
+ }
1069
+
1070
+ /**
1071
+ * Checks if a widget with the same ID as the pending widget exists in the provided widget registry instance.
1072
+ *
1073
+ * @param {Array} [widgetRegistryInstance=[]] - An array of widget objects to check.
1074
+ * @param {Object} widgetPendingRegistry - The widget to check for in the registry.
1075
+ * @returns {boolean} - `true` if a widget with the same ID is found in the registry; otherwise, `false`.
1076
+ */
1077
+ widgetRegistryHasWidget(widgetRegistryInstance = [], widgetPendingRegistry) {
1078
+ return widgetRegistryInstance.includes((widget) => widget.widgetId === widgetPendingRegistry.widgetId);
1079
+ }
1080
+
1081
+ /**
1082
+ * Registers widgets for the default 'dashboard' dashboard.
1083
+ *
1084
+ * @param {Array} [widgets=[]] - An array of widget objects to register.
1085
+ * @returns {void}
1086
+ */
1087
+ registerDashboardWidgets(widgets = []) {
1088
+ this.registerWidgets('dashboard', widgets);
1089
+ }
1090
+
1091
+ /**
1092
+ * Registers default widgets for the default 'dashboard' dashboard.
1093
+ *
1094
+ * @param {Array} [widgets=[]] - An array of default widget objects to register.
1095
+ * @returns {void}
1096
+ */
1097
+ registerDefaultDashboardWidgets(widgets = []) {
1098
+ this.registerWidgets('dashboard', widgets, 'defaultWidgets');
1099
+ }
1100
+
1101
+ /**
1102
+ * Registers default widgets for a specified dashboard.
1103
+ *
1104
+ * @param {String} dashboardName
1105
+ * @param {Array} [widgets=[]] - An array of default widget objects to register.
1106
+ * @returns {void}
1107
+ */
1108
+ registerDefaultWidgets(dashboardName, widgets = []) {
1109
+ this.registerWidgets(dashboardName, widgets, 'defaultWidgets');
1110
+ }
1111
+
1112
+ /**
1113
+ * Retrieves widgets for a specific dashboard.
1114
+ *
1115
+ * @param {string} dashboardName - The name of the dashboard to retrieve widgets for.
1116
+ * @param {string} [type='widgets'] - The type of widgets to retrieve (e.g., 'widgets', 'defaultWidgets').
1117
+ * @returns {Array} - An array of widgets for the specified dashboard and type.
1118
+ */
1119
+ getWidgets(dashboardName, type = 'widgets') {
1120
+ const typeKey = pluralize(type);
1121
+ const internalDashboardRegistryName = this.createInternalDashboardName(dashboardName);
1122
+ return isArray(this[internalDashboardRegistryName][typeKey]) ? this[internalDashboardRegistryName][typeKey] : [];
1123
+ }
1124
+
1125
+ /**
1126
+ * Retrieves default widgets for a specific dashboard.
1127
+ *
1128
+ * @param {string} dashboardName - The name of the dashboard to retrieve default widgets for.
1129
+ * @returns {Array} - An array of default widgets for the specified dashboard.
1130
+ */
1131
+ getDefaultWidgets(dashboardName) {
1132
+ return this.getWidgets(dashboardName, 'defaultWidgets');
1133
+ }
1134
+
1135
+ /**
1136
+ * Retrieves widgets for the default 'dashboard' dashboard.
1137
+ *
1138
+ * @returns {Array} - An array of widgets for the default 'dashboard' dashboard.
1139
+ */
1140
+ getDashboardWidgets() {
1141
+ return this.getWidgets('dashboard');
1142
+ }
1143
+
1144
+ /**
1145
+ * Retrieves default widgets for the default 'dashboard' dashboard.
1146
+ *
1147
+ * @returns {Array} - An array of default widgets for the default 'dashboard' dashboard.
824
1148
  */
825
1149
  getDefaultDashboardWidgets() {
826
- return this.dashboardWidgets.defaultWidgets;
1150
+ return this.getWidgets('dashboard', 'defaultWidgets');
827
1151
  }
828
1152
 
829
1153
  /**
830
- * Creates a dashboard widget object from the given widget configuration.
1154
+ * Creates an internal name for a dashboard based on its given name.
831
1155
  *
832
- * @param {Object} widget - The widget configuration object.
833
- * @param {string} widget.widgetId - The unique identifier for the widget.
1156
+ * @param {string} dashboardName - The name of the dashboard.
1157
+ * @returns {string} - The internal name for the dashboard, formatted as `${dashboardName}Widgets`.
1158
+ */
1159
+ createInternalDashboardName(dashboardName) {
1160
+ return `${camelize(dashboardName.replace(/[^a-zA-Z0-9]/g, '-'))}Widgets`;
1161
+ }
1162
+
1163
+ /**
1164
+ * Creates a new widget object from a widget definition.
1165
+ * If the component is a function, it is registered with the host application.
1166
+ *
1167
+ * @param {Object} widget - The widget definition.
1168
+ * @param {string} widget.widgetId - The unique ID of the widget.
834
1169
  * @param {string} widget.name - The name of the widget.
835
- * @param {string} widget.description - The description of the widget.
836
- * @param {string} widget.icon - The icon for the widget.
837
- * @param {(Function|string)} widget.component - The component class or name for the widget.
838
- * @param {Object} widget.grid_options - Grid options for the widget layout.
839
- * @param {Object} widget.options - Additional options for the widget.
840
- * @returns {Object} A new widget object with properties derived from the input configuration.
841
- * @memberof UniverseService
1170
+ * @param {string} [widget.description] - A description of the widget.
1171
+ * @param {string} [widget.icon] - An icon for the widget.
1172
+ * @param {Function|string} [widget.component] - A component definition or name for the widget.
1173
+ * @param {Object} [widget.grid_options] - Grid options for the widget.
1174
+ * @param {Object} [widget.options] - Additional options for the widget.
1175
+ * @returns {Object} - The newly created widget object.
842
1176
  */
843
1177
  _createDashboardWidget(widget) {
844
1178
  // Extract properties from the widget object
@@ -872,16 +1206,31 @@ export default class UniverseService extends Service.extend(Evented) {
872
1206
  }
873
1207
 
874
1208
  /**
875
- * Creates a unique hash from a component's definition. This hash is used as an identifier
876
- * for the component when a direct identifier (widgetId) or a name is not available.
1209
+ * Generates a unique hash for a widget component based on its function definition.
1210
+ * This method delegates the hash creation to the `_createHashFromFunctionDefinition` method.
877
1211
  *
878
- * @param {Function} component - The component class or constructor function.
879
- * @returns {string} A unique hash string representing the component's definition.
880
- * @memberof UniverseService
1212
+ * @param {Function} component - The function representing the widget component.
1213
+ * @returns {string} - The unique hash representing the widget component.
881
1214
  */
882
1215
  _createUniqueWidgetHashFromDefinition(component) {
883
- if (typeof component.toString === 'function') {
884
- let definition = component.toString();
1216
+ return this._createHashFromFunctionDefinition(component);
1217
+ }
1218
+
1219
+ /**
1220
+ * Creates a hash value from a function definition. The hash is generated based on the function's string representation.
1221
+ * If the function has a name, it returns that name. Otherwise, it converts the function's string representation
1222
+ * into a hash value. This is done by iterating over the characters of the string and performing a simple hash calculation.
1223
+ *
1224
+ * @param {Function} func - The function whose definition will be hashed.
1225
+ * @returns {string} - The hash value derived from the function's definition. If the function has a name, it is returned directly.
1226
+ */
1227
+ _createHashFromFunctionDefinition(func) {
1228
+ if (func.name) {
1229
+ return func.name;
1230
+ }
1231
+
1232
+ if (typeof func.toString === 'function') {
1233
+ let definition = func.toString();
885
1234
  let hash = 0;
886
1235
  for (let i = 0; i < definition.length; i++) {
887
1236
  const char = definition.charCodeAt(i);
@@ -891,20 +1240,7 @@ export default class UniverseService extends Service.extend(Evented) {
891
1240
  return hash.toString(16);
892
1241
  }
893
1242
 
894
- return component.name;
895
- }
896
-
897
- /**
898
- * Registers a new settings menu item.
899
- *
900
- * @method registerSettingsMenuItem
901
- * @public
902
- * @memberof UniverseService
903
- * @param {String} title The title of the item
904
- * @param {Object} options Additional options for the item
905
- */
906
- registerSettingsMenuItem(title, options = {}) {
907
- this.registerMenuItem('settings', title, options);
1243
+ return func.name;
908
1244
  }
909
1245
 
910
1246
  /**
@@ -992,7 +1328,7 @@ export default class UniverseService extends Service.extend(Evented) {
992
1328
  const componentParams = this._getOption(options, 'componentParams', {});
993
1329
  const renderComponentInPlace = this._getOption(options, 'renderComponentInPlace', false);
994
1330
  const slug = this._getOption(options, 'slug', dasherize(title));
995
- const view = this._getOption(options, 'view');
1331
+ const view = this._getOption(options, 'view', dasherize(title));
996
1332
  const queryParams = this._getOption(options, 'queryParams', {});
997
1333
  const index = this._getOption(options, 'index', 0);
998
1334
  const onClick = this._getOption(options, 'onClick', null);
@@ -1006,6 +1342,12 @@ export default class UniverseService extends Service.extend(Evented) {
1006
1342
  const inlineClass = this._getOption(options, 'inlineClass', null);
1007
1343
  const wrapperClass = this._getOption(options, 'wrapperClass', null);
1008
1344
  const overwriteWrapperClass = this._getOption(options, 'overwriteWrapperClass', false);
1345
+ const id = this._getOption(options, 'id', dasherize(title));
1346
+ const type = this._getOption(options, 'type', null);
1347
+ const buttonType = this._getOption(options, 'buttonType', null);
1348
+ const permission = this._getOption(options, 'permission', null);
1349
+ const disabled = this._getOption(options, 'disabled', null);
1350
+ const isLoading = this._getOption(options, 'isLoading', null);
1009
1351
 
1010
1352
  // dasherize route segments
1011
1353
  if (typeof route === 'string') {
@@ -1015,9 +1357,11 @@ export default class UniverseService extends Service.extend(Evented) {
1015
1357
  .join('.');
1016
1358
  }
1017
1359
 
1018
- // todo: create menu item class
1360
+ // @todo: create menu item class
1019
1361
  const menuItem = {
1362
+ id,
1020
1363
  title,
1364
+ text: title,
1021
1365
  route,
1022
1366
  icon,
1023
1367
  priority,
@@ -1040,6 +1384,11 @@ export default class UniverseService extends Service.extend(Evented) {
1040
1384
  inlineClass,
1041
1385
  wrapperClass,
1042
1386
  overwriteWrapperClass,
1387
+ type,
1388
+ buttonType,
1389
+ permission,
1390
+ disabled,
1391
+ isLoading,
1043
1392
  };
1044
1393
 
1045
1394
  return menuItem;
@@ -1117,6 +1466,7 @@ export default class UniverseService extends Service.extend(Evented) {
1117
1466
  engineInstance.register(`component:${dasherize(componentClass.name.replace('Component', ''))}`, componentClass);
1118
1467
  if (options && typeof options.registerAs === 'string') {
1119
1468
  engineInstance.register(`component:${options.registerAs}`, componentClass);
1469
+ this.trigger('component.registered', componentClass, engineInstance);
1120
1470
  }
1121
1471
  }
1122
1472
  }
@@ -1154,6 +1504,7 @@ export default class UniverseService extends Service.extend(Evented) {
1154
1504
  if (sharedService) {
1155
1505
  // Register the service in the target engine
1156
1506
  targetEngineInstance.register(`service:${serviceName}`, sharedService, { instantiate: false });
1507
+ this.trigger('service.registered', serviceName, targetEngineInstance);
1157
1508
  }
1158
1509
  }
1159
1510
  }
@@ -1272,6 +1623,7 @@ export default class UniverseService extends Service.extend(Evented) {
1272
1623
  // store loaded instance to engineInstances for booting
1273
1624
  engineInstances[name][instanceId] = engineInstance;
1274
1625
 
1626
+ this.trigger('engine.loaded', engineInstance);
1275
1627
  return engineInstance.boot().then(() => {
1276
1628
  return engineInstance;
1277
1629
  });
@@ -1337,6 +1689,35 @@ export default class UniverseService extends Service.extend(Evented) {
1337
1689
  return null;
1338
1690
  }
1339
1691
 
1692
+ /**
1693
+ * Returns a promise that resolves when the `enginesBooted` property is set to true.
1694
+ * The promise will reject with a timeout error if the property does not become true within the specified timeout.
1695
+ *
1696
+ * @function booting
1697
+ * @returns {Promise<void>} A promise that resolves when `enginesBooted` is true or rejects with an error after a timeout.
1698
+ */
1699
+ booting() {
1700
+ return new Promise((resolve, reject) => {
1701
+ const check = () => {
1702
+ if (this.enginesBooted === true) {
1703
+ this.trigger('booted');
1704
+ clearInterval(intervalId);
1705
+ resolve();
1706
+ }
1707
+ };
1708
+
1709
+ const intervalId = setInterval(check, 100);
1710
+ later(
1711
+ this,
1712
+ () => {
1713
+ clearInterval(intervalId);
1714
+ reject(new Error('Timeout: Universe was unable to boot engines'));
1715
+ },
1716
+ 5000
1717
+ );
1718
+ });
1719
+ }
1720
+
1340
1721
  /**
1341
1722
  * Boot all installed engines, ensuring dependencies are resolved.
1342
1723
  *
@@ -1359,10 +1740,13 @@ export default class UniverseService extends Service.extend(Evented) {
1359
1740
  owner = getOwner(this);
1360
1741
  }
1361
1742
 
1743
+ // Set application instance
1744
+ this.applicationInstance = owner;
1745
+
1362
1746
  const tryBootEngine = (extension) => {
1363
1747
  this.loadEngine(extension.name).then((engineInstance) => {
1364
1748
  if (engineInstance.base && engineInstance.base.setupExtension) {
1365
- if (booted.includes(extension.name)) {
1749
+ if (this.bootedExtensions.includes(extension.name)) {
1366
1750
  return;
1367
1751
  }
1368
1752
 
@@ -1376,6 +1760,8 @@ export default class UniverseService extends Service.extend(Evented) {
1376
1760
 
1377
1761
  engineInstance.base.setupExtension(owner, engineInstance, this);
1378
1762
  booted.push(extension.name);
1763
+ this.bootedExtensions.pushObject(extension.name);
1764
+ this.trigger('extension.booted', extension);
1379
1765
  debug(`Booted : ${extension.name}`);
1380
1766
 
1381
1767
  // Try booting pending engines again
@@ -1388,7 +1774,7 @@ export default class UniverseService extends Service.extend(Evented) {
1388
1774
  const stillPending = [];
1389
1775
 
1390
1776
  pending.forEach(({ extension, engineInstance }) => {
1391
- if (booted.includes(extension.name)) {
1777
+ if (this.bootedExtensions.includes(extension.name)) {
1392
1778
  return;
1393
1779
  }
1394
1780
 
@@ -1398,6 +1784,8 @@ export default class UniverseService extends Service.extend(Evented) {
1398
1784
  if (allDependenciesBooted) {
1399
1785
  engineInstance.base.setupExtension(owner, engineInstance, this);
1400
1786
  booted.push(extension.name);
1787
+ this.bootedExtensions.pushObject(extension.name);
1788
+ this.trigger('extension.booted', extension);
1401
1789
  debug(`Booted : ${extension.name}`);
1402
1790
  } else {
1403
1791
  stillPending.push({ extension, engineInstance });
@@ -1411,10 +1799,12 @@ export default class UniverseService extends Service.extend(Evented) {
1411
1799
  pending.push(...stillPending);
1412
1800
  };
1413
1801
 
1414
- loadInstalledExtensions(additionalCoreExtensions).then((extensions) => {
1802
+ return loadInstalledExtensions(additionalCoreExtensions).then((extensions) => {
1415
1803
  extensions.forEach((extension) => {
1416
1804
  tryBootEngine(extension);
1417
1805
  });
1806
+
1807
+ this.enginesBooted = true;
1418
1808
  });
1419
1809
  }
1420
1810
 
@@ -1440,6 +1830,9 @@ export default class UniverseService extends Service.extend(Evented) {
1440
1830
  owner = getOwner(this);
1441
1831
  }
1442
1832
 
1833
+ // Set application instance
1834
+ this.applicationInstance = owner;
1835
+
1443
1836
  const tryBootEngine = (extension) => {
1444
1837
  this.loadEngine(extension.name).then((engineInstance) => {
1445
1838
  if (engineInstance.base && engineInstance.base.setupExtension) {
@@ -1455,6 +1848,8 @@ export default class UniverseService extends Service.extend(Evented) {
1455
1848
 
1456
1849
  engineInstance.base.setupExtension(owner, engineInstance, this);
1457
1850
  booted.push(extension.name);
1851
+ this.bootedExtensions.pushObject(extension.name);
1852
+ this.trigger('extension.booted', extension);
1458
1853
  debug(`Booted : ${extension.name}`);
1459
1854
 
1460
1855
  // Try booting pending engines again
@@ -1473,6 +1868,8 @@ export default class UniverseService extends Service.extend(Evented) {
1473
1868
  if (allDependenciesBooted) {
1474
1869
  engineInstance.base.setupExtension(owner, engineInstance, this);
1475
1870
  booted.push(extension.name);
1871
+ this.bootedExtensions.pushObject(extension.name);
1872
+ this.trigger('extension.booted', extension);
1476
1873
  debug(`Booted : ${extension.name}`);
1477
1874
  } else {
1478
1875
  stillPending.push({ extension, engineInstance });
@@ -1486,13 +1883,26 @@ export default class UniverseService extends Service.extend(Evented) {
1486
1883
  pending.push(...stillPending);
1487
1884
  };
1488
1885
 
1489
- loadExtensions().then((extensions) => {
1886
+ return loadExtensions().then((extensions) => {
1490
1887
  extensions.forEach((extension) => {
1491
1888
  tryBootEngine(extension);
1492
1889
  });
1890
+
1891
+ this.enginesBooted = true;
1493
1892
  });
1494
1893
  }
1495
1894
 
1895
+ /**
1896
+ * Checks if an extension has been booted.
1897
+ *
1898
+ * @param {String} name
1899
+ * @return {Boolean}
1900
+ * @memberof UniverseService
1901
+ */
1902
+ didBootEngine(name) {
1903
+ return this.bootedExtensions.includes(name);
1904
+ }
1905
+
1496
1906
  /**
1497
1907
  * Alias for intl service `t`
1498
1908
  *