@cccsaurora/howler-ui 2.14.0-dev.232 → 2.14.0-dev.245

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.
@@ -0,0 +1,243 @@
1
+ import { MainMenuInsertOperation } from '../plugins/store';
2
+ import { isNil } from 'lodash-es';
3
+ class AppMenuBuilder {
4
+ items;
5
+ indexMap;
6
+ constructor(defaultMenu) {
7
+ this.items = defaultMenu;
8
+ this.updateMenuMap();
9
+ }
10
+ /**
11
+ * Applies a collection of Menu Operation objects created by the plugin system
12
+ *
13
+ * @param operations Operations created by the plugin system
14
+ */
15
+ applyOperations(operations) {
16
+ for (const operation of operations) {
17
+ switch (operation.operation) {
18
+ case MainMenuInsertOperation.Insert:
19
+ // Inserts at end or adds to sub-elements
20
+ this.insert(operation.targetId, operation.item);
21
+ break;
22
+ case MainMenuInsertOperation.InsertBefore:
23
+ // Inserts before target element
24
+ this.insertBefore(operation.targetId, operation.item);
25
+ break;
26
+ case MainMenuInsertOperation.InsertAfter:
27
+ // Inserts after target element
28
+ this.insertAfter(operation.targetId, operation.item);
29
+ break;
30
+ }
31
+ this.updateMenuMap();
32
+ }
33
+ }
34
+ /**
35
+ * Get the completed menu structure
36
+ */
37
+ get menu() {
38
+ return this.items;
39
+ }
40
+ /**
41
+ * Insert provided menu item at the menu object identified by targetId.
42
+ *
43
+ * If the target menu is a group then item will be placed at end of sub items
44
+ *
45
+ * If the target menu is a standard item then group will be created and new item added, warning this removes
46
+ * the route from the new group parent item, be sure to add it back.
47
+ *
48
+ * If the target is 'root' then item will be added to end of root menu
49
+ *
50
+ * @param targetId Identifier of menu to insert to
51
+ * @param item Menu Item to insert
52
+ */
53
+ insert(targetId, item) {
54
+ const menuLocation = this.indexOfMenuId(targetId);
55
+ const target = this.menuFromIndex(menuLocation.index, menuLocation.subIndex);
56
+ if (!Array.isArray(target) && this.isGroupElement(target)) {
57
+ if (item.type === 'divider') {
58
+ console.warn(`Skipping DIVIDER Operation: INSERT on Target: ${targetId}, Dividers cannot be inserted to sub-menus`);
59
+ return;
60
+ }
61
+ const group = target.element;
62
+ const newItem = { ...item.element, nested: true };
63
+ group.items = [...group.items, newItem];
64
+ return;
65
+ }
66
+ if (Array.isArray(target)) {
67
+ target.push(item);
68
+ return;
69
+ }
70
+ if (menuLocation.index !== -1 && isNil(menuLocation.subIndex)) {
71
+ if (item.type === 'divider') {
72
+ console.warn(`Skipping DIVIDER Operation: INSERT on Target: ${targetId}, Dividers cannot be inserted to sub-menus`);
73
+ return;
74
+ }
75
+ if (!this.isItemElement(target)) {
76
+ return;
77
+ }
78
+ const header = target.element;
79
+ const inserted = { ...item.element, nested: true };
80
+ const newGroup = {
81
+ type: 'group',
82
+ element: {
83
+ id: header.id,
84
+ open: true,
85
+ i18nKey: header.i18nKey,
86
+ title: header.text,
87
+ userPropValidators: header.userPropValidators,
88
+ icon: header.icon,
89
+ items: [inserted]
90
+ }
91
+ };
92
+ this.items[menuLocation.index] = newGroup;
93
+ return;
94
+ }
95
+ }
96
+ /**
97
+ * Insert provided menu item before the menu object identified by targetId.
98
+ *
99
+ * @param targetId Identifier of menu to insert to
100
+ * @param item Menu Item to insert
101
+ */
102
+ insertBefore(targetId, item) {
103
+ const menuLocation = this.indexOfMenuId(targetId);
104
+ if (!isNil(menuLocation.subIndex)) {
105
+ if (item.type === 'divider') {
106
+ console.warn(`Skipping DIVIDER Operation: INSERT on Target: ${targetId}, Dividers cannot be inserted to sub-menus`);
107
+ return;
108
+ }
109
+ const parentElement = this.menuFromIndex(menuLocation.index);
110
+ if (!Array.isArray(parentElement) && this.isGroupElement(parentElement)) {
111
+ const group = parentElement.element;
112
+ const newItem = { ...item.element, nested: true };
113
+ group.items = [
114
+ ...group.items.slice(0, menuLocation.subIndex),
115
+ newItem,
116
+ ...group.items.slice(menuLocation.subIndex)
117
+ ];
118
+ }
119
+ return;
120
+ }
121
+ // Root level insertion before target index
122
+ if (menuLocation.index < 0) {
123
+ menuLocation.index = 0;
124
+ }
125
+ this.items = [...this.items.slice(0, menuLocation.index), item, ...this.items.slice(menuLocation.index)];
126
+ }
127
+ /**
128
+ * Insert provided menu item after the menu object identified by targetId.
129
+ *
130
+ * @param targetId Identifier of menu to insert to
131
+ * @param item Menu Item to insert
132
+ */
133
+ insertAfter(targetId, item) {
134
+ const menuLocation = this.indexOfMenuId(targetId);
135
+ if (!isNil(menuLocation.subIndex)) {
136
+ if (item.type === 'divider') {
137
+ console.warn(`Skipping DIVIDER Operation: INSERT on Target: ${targetId}, Dividers cannot be inserted to sub-menus`);
138
+ return;
139
+ }
140
+ const parentElement = this.menuFromIndex(menuLocation.index);
141
+ if (!Array.isArray(parentElement) && this.isGroupElement(parentElement)) {
142
+ const group = parentElement.element;
143
+ const newItem = { ...item.element, nested: true };
144
+ group.items = [
145
+ ...group.items.slice(0, menuLocation.subIndex + 1),
146
+ newItem,
147
+ ...group.items.slice(menuLocation.subIndex + 1)
148
+ ];
149
+ }
150
+ return;
151
+ }
152
+ // Root level insertion after target index
153
+ if (menuLocation.index < 0) {
154
+ menuLocation.index = this.items.length;
155
+ }
156
+ this.items = [...this.items.slice(0, menuLocation.index + 1), item, ...this.items.slice(menuLocation.index + 1)];
157
+ }
158
+ /**
159
+ * Locates menu location in menu structure by menu id.
160
+ *
161
+ * @param id Menu Id to search for
162
+ * @return {index: number, subIndex: number} A dictionary containing indexes needed to access Menu Item
163
+ */
164
+ indexOfMenuId(id) {
165
+ if (id === 'root') {
166
+ // Root item so return entire menu
167
+ return { index: -1 };
168
+ }
169
+ else if (id in this.indexMap) {
170
+ // Item exists, check if it's a subitem
171
+ if ('parent' in this.indexMap[id]) {
172
+ return { index: this.indexMap[id].parent, subIndex: this.indexMap[id].index };
173
+ }
174
+ return { index: this.indexMap[id].index };
175
+ }
176
+ else {
177
+ throw new Error(`Menu element with id of '${id}' not found.`);
178
+ }
179
+ }
180
+ /**
181
+ * Grabs a menu by indexes, helper function to account for an index of -1
182
+ * to represent the root menu
183
+ *
184
+ * @param index First level index
185
+ * @param subIndex Second level index
186
+ * @return {} Menu item
187
+ */
188
+ menuFromIndex(index, subIndex) {
189
+ if (index === -1) {
190
+ return this.items;
191
+ }
192
+ if (isNil(subIndex)) {
193
+ return this.items[index];
194
+ }
195
+ const menuItem = this.items[index];
196
+ if (menuItem.type === 'group') {
197
+ return menuItem.element.items[subIndex];
198
+ }
199
+ throw new Error(`Menu item at index ${index} is not a group and does not have sub-items.`);
200
+ }
201
+ /**
202
+ * Determine if provided element is a group element
203
+ *
204
+ * @param elem Element to check
205
+ * @private
206
+ */
207
+ isGroupElement(elem) {
208
+ return !!elem && typeof elem === 'object' && elem.type === 'group';
209
+ }
210
+ /**
211
+ * Determine if provided element is an item element
212
+ *
213
+ * @param elem Element to check
214
+ * @private
215
+ */
216
+ isItemElement(elem) {
217
+ return !!elem && typeof elem === 'object' && elem.type === 'item';
218
+ }
219
+ /**
220
+ * Creates a flat list of menu items and subitems by menu id
221
+ * for quick lookup.
222
+ *
223
+ * @private
224
+ */
225
+ updateMenuMap() {
226
+ const indexMap = {};
227
+ for (let index = 0; index < this.items.length; index++) {
228
+ const menuItem = this.items[index];
229
+ if (menuItem.type === 'divider') {
230
+ continue;
231
+ }
232
+ indexMap[menuItem.element.id] = { index };
233
+ if (menuItem.type === 'group') {
234
+ for (let subIndex = 0; subIndex < menuItem.element.items.length; subIndex++) {
235
+ const subMenuItem = menuItem.element.items[subIndex];
236
+ indexMap[subMenuItem.id] = { index: subIndex, parent: index };
237
+ }
238
+ }
239
+ }
240
+ this.indexMap = indexMap;
241
+ }
242
+ }
243
+ export default AppMenuBuilder;