@itfin/components 1.4.40 → 1.5.1

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.
Files changed (187) hide show
  1. package/package.json +17 -20
  2. package/src/ITFSettings.js +6 -0
  3. package/src/assets/scss/components/_button.scss +118 -0
  4. package/src/assets/scss/components/_text-field.scss +22 -8
  5. package/src/components/alert/AlertBanner.vue +75 -0
  6. package/src/components/app/App.vue +6 -3
  7. package/src/components/button/Button.vue +3 -1
  8. package/src/components/button/NativeButton.js +4 -0
  9. package/src/components/button/index.stories.js +2 -2
  10. package/src/components/checkbox/Checkbox.vue +2 -1
  11. package/src/components/checkbox/RadioBox.vue +13 -7
  12. package/src/components/copyToClipboard/CopyToClipboard.vue +4 -1
  13. package/src/components/customize/PropertiesList.vue +0 -2
  14. package/src/components/customize/PropertiesPopupMenu.vue +1 -1
  15. package/src/components/customize/PropertyItem.vue +6 -24
  16. package/src/components/datepicker/DatePicker.vue +3 -1
  17. package/src/components/datepicker/DatePickerInline.vue +2 -2
  18. package/src/components/datepicker/DateRangePickerInline.vue +6 -1
  19. package/src/components/dropdown/Dropdown.vue +1 -1
  20. package/src/components/dropdown/DropdownMenu.vue +1 -1
  21. package/src/components/editable/EditButton.vue +1 -1
  22. package/src/components/editor/plugins.js +1012 -0
  23. package/src/components/filter/FilterBadge.vue +17 -3
  24. package/src/components/filter/FilterFacetsList.vue +67 -13
  25. package/src/components/filter/FilterPanel.vue +8 -21
  26. package/src/components/filter/NewFilter.vue +305 -0
  27. package/src/components/form/Label.vue +5 -5
  28. package/src/components/icon/components/nomi-ai-alt.vue +5 -0
  29. package/src/components/icon/components/nomi-bell.vue +5 -0
  30. package/src/components/icon/components/nomi-budget.vue +4 -0
  31. package/src/components/icon/components/nomi-calendar-payment.vue +10 -0
  32. package/src/components/icon/components/nomi-card-plus.vue +1 -0
  33. package/src/components/icon/components/nomi-cash-provider.vue +9 -0
  34. package/src/components/icon/components/nomi-category-edit.vue +5 -0
  35. package/src/components/icon/components/nomi-delta.vue +7 -0
  36. package/src/components/icon/components/nomi-exit-right.vue +4 -0
  37. package/src/components/icon/components/nomi-help.vue +2 -3
  38. package/src/components/icon/components/nomi-lock.vue +1 -1
  39. package/src/components/icon/components/nomi-project.vue +2 -2
  40. package/src/components/icon/components/nomi-refresh-off.vue +4 -0
  41. package/src/components/icon/components/nomi-refresh.vue +4 -0
  42. package/src/components/icon/components/nomi-scissors.vue +1 -1
  43. package/src/components/icon/components/nomi-start.vue +28 -0
  44. package/src/components/icon/components/nomi-table-view.vue +1 -4
  45. package/src/components/icon/components/nomi-transactions-delete.vue +5 -0
  46. package/src/components/icon/components/nomi-type-array.vue +6 -0
  47. package/src/components/icon/components/nomi-type-boolean.vue +5 -0
  48. package/src/components/icon/components/nomi-type-date.vue +4 -0
  49. package/src/components/icon/components/nomi-type-null.vue +4 -0
  50. package/src/components/icon/components/nomi-type-number.vue +4 -0
  51. package/src/components/icon/components/nomi-type-object.vue +4 -0
  52. package/src/components/icon/components/nomi-type-string.vue +4 -0
  53. package/src/components/icon/components/nomi-unarchive.vue +17 -0
  54. package/src/components/icon/components/nomi-unlink.vue +10 -0
  55. package/src/components/icon/components/nomi-user.vue +3 -3
  56. package/src/components/icon/components/nomi-warning-triangle.vue +6 -0
  57. package/src/components/icon/components/nomi-warning_triangle_filled.vue +6 -0
  58. package/src/components/icon/convert-icons.js +3 -0
  59. package/src/components/icon/icons.js +390 -312
  60. package/src/components/icon/new-icons/ai-alt.svg +4 -0
  61. package/src/components/icon/new-icons/arrow-right-alt.svg +3 -0
  62. package/src/components/icon/new-icons/arrow-right.svg +3 -0
  63. package/src/components/icon/new-icons/arrow_left.svg +3 -0
  64. package/src/components/icon/new-icons/automation.svg +4 -0
  65. package/src/components/icon/new-icons/balance.svg +3 -0
  66. package/src/components/icon/new-icons/balance_turnover.svg +4 -0
  67. package/src/components/icon/new-icons/bar-horizontal.svg +6 -0
  68. package/src/components/icon/new-icons/bell.svg +4 -0
  69. package/src/components/icon/new-icons/calendar-payment.svg +9 -0
  70. package/src/components/icon/new-icons/card-plus.svg +1 -0
  71. package/src/components/icon/new-icons/cash-provider.svg +8 -0
  72. package/src/components/icon/new-icons/cash-repeat.svg +5 -0
  73. package/src/components/icon/new-icons/cash.svg +3 -0
  74. package/src/components/icon/new-icons/cashflow.svg +3 -0
  75. package/src/components/icon/new-icons/category-edit.svg +4 -0
  76. package/src/components/icon/new-icons/category.svg +4 -0
  77. package/src/components/icon/new-icons/category_alt.svg +3 -0
  78. package/src/components/icon/new-icons/chart-bars.svg +5 -0
  79. package/src/components/icon/new-icons/chart-donut.svg +3 -0
  80. package/src/components/icon/new-icons/chart-funnel.svg +5 -0
  81. package/src/components/icon/new-icons/chart-kpi.svg +7 -0
  82. package/src/components/icon/new-icons/chart-line.svg +4 -0
  83. package/src/components/icon/new-icons/chart-lines.svg +5 -0
  84. package/src/components/icon/new-icons/check-alt.svg +3 -0
  85. package/src/components/icon/new-icons/check.svg +3 -0
  86. package/src/components/icon/new-icons/chevron-down.svg +3 -0
  87. package/src/components/icon/new-icons/chevron-left.svg +3 -0
  88. package/src/components/icon/new-icons/chevron-right.svg +3 -0
  89. package/src/components/icon/new-icons/chevron-up.svg +3 -0
  90. package/src/components/icon/new-icons/collapse.svg +6 -0
  91. package/src/components/icon/new-icons/control-panel.svg +7 -0
  92. package/src/components/icon/new-icons/credit.svg +3 -0
  93. package/src/components/icon/new-icons/currencies.svg +3 -0
  94. package/src/components/icon/new-icons/debt.svg +3 -0
  95. package/src/components/icon/new-icons/delta.svg +6 -0
  96. package/src/components/icon/new-icons/demo.svg +6 -0
  97. package/src/components/icon/new-icons/dev.svg +3 -0
  98. package/src/components/icon/new-icons/dots.svg +5 -0
  99. package/src/components/icon/new-icons/duplicate.svg +4 -0
  100. package/src/components/icon/new-icons/exit-right.svg +3 -0
  101. package/src/components/icon/new-icons/export.svg +3 -0
  102. package/src/components/icon/new-icons/file.svg +3 -0
  103. package/src/components/icon/new-icons/folder.svg +3 -0
  104. package/src/components/icon/new-icons/goods-turnover.svg +3 -0
  105. package/src/components/icon/new-icons/goods.svg +4 -0
  106. package/src/components/icon/new-icons/help-alt.svg +3 -0
  107. package/src/components/icon/new-icons/help.svg +2 -3
  108. package/src/components/icon/new-icons/history.svg +6 -0
  109. package/src/components/icon/new-icons/integration.svg +3 -0
  110. package/src/components/icon/new-icons/link.svg +5 -0
  111. package/src/components/icon/new-icons/lock.svg +1 -1
  112. package/src/components/icon/new-icons/menu.svg +5 -0
  113. package/src/components/icon/new-icons/minus.svg +3 -0
  114. package/src/components/icon/new-icons/payment_calendar.svg +3 -0
  115. package/src/components/icon/new-icons/pc.svg +3 -0
  116. package/src/components/icon/new-icons/pen-alt.svg +3 -0
  117. package/src/components/icon/new-icons/planFact.svg +4 -0
  118. package/src/components/icon/new-icons/pnl.svg +7 -0
  119. package/src/components/icon/new-icons/project.svg +2 -2
  120. package/src/components/icon/new-icons/project_alt.svg +3 -0
  121. package/src/components/icon/new-icons/project_alt2.svg +3 -0
  122. package/src/components/icon/new-icons/promo.svg +3 -0
  123. package/src/components/icon/new-icons/refresh-off.svg +3 -0
  124. package/src/components/icon/new-icons/refresh.svg +3 -0
  125. package/src/components/icon/new-icons/scissors.svg +1 -1
  126. package/src/components/icon/new-icons/segment.svg +3 -0
  127. package/src/components/icon/new-icons/start.svg +27 -0
  128. package/src/components/icon/new-icons/strongbox.svg +3 -0
  129. package/src/components/icon/new-icons/subscription.svg +3 -0
  130. package/src/components/icon/new-icons/table-view.svg +1 -4
  131. package/src/components/icon/new-icons/time.svg +3 -0
  132. package/src/components/icon/new-icons/transactions_alt.svg +6 -0
  133. package/src/components/icon/new-icons/transactions_delete.svg +4 -0
  134. package/src/components/icon/new-icons/type-array.svg +5 -0
  135. package/src/components/icon/new-icons/type-boolean.svg +4 -0
  136. package/src/components/icon/new-icons/type-date.svg +3 -0
  137. package/src/components/icon/new-icons/type-null.svg +3 -0
  138. package/src/components/icon/new-icons/type-number.svg +3 -0
  139. package/src/components/icon/new-icons/type-object.svg +3 -0
  140. package/src/components/icon/new-icons/type-string.svg +3 -0
  141. package/src/components/icon/new-icons/types.svg +6 -0
  142. package/src/components/icon/new-icons/unarchive.svg +16 -0
  143. package/src/components/icon/new-icons/unlink.svg +9 -0
  144. package/src/components/icon/new-icons/user.svg +3 -3
  145. package/src/components/icon/new-icons/user_plus.svg +10 -0
  146. package/src/components/icon/new-icons/warehouse.svg +3 -0
  147. package/src/components/icon/new-icons/warning_triangle.svg +5 -0
  148. package/src/components/icon/new-icons/warning_triangle_filled.svg +5 -0
  149. package/src/components/kanban/BoardCard.vue +1 -1
  150. package/src/components/kanban/BoardCardTimer.vue +1 -1
  151. package/src/components/modal/DeleteConfirmModal.vue +10 -6
  152. package/src/components/modal/ItemEditor.vue +1 -1
  153. package/src/components/modal/Modal.vue +1 -1
  154. package/src/components/overlay/SensitiveOverlay.vue +2 -4
  155. package/src/components/panels/Panel.vue +110 -23
  156. package/src/components/panels/PanelItemEdit.vue +8 -6
  157. package/src/components/panels/PanelList.vue +164 -40
  158. package/src/components/panels/helpers.ts +41 -13
  159. package/src/components/popover/Popover.vue +105 -22
  160. package/src/components/segmented-control/SegmentedControl.vue +9 -3
  161. package/src/components/sortable/draggable.js +1 -1
  162. package/src/components/table/Table2.vue +68 -67
  163. package/src/components/table/TableBody.vue +17 -22
  164. package/src/components/table/TableGroup.vue +40 -24
  165. package/src/components/table/TableHeader.vue +86 -81
  166. package/src/components/table/TableRowToggle.vue +1 -9
  167. package/src/components/table/TableRows.vue +49 -55
  168. package/src/components/table/mobile.js +4 -0
  169. package/src/components/table/table2.scss +34 -15
  170. package/src/components/text-field/MoneyField.vue +10 -4
  171. package/src/components/text-field/TextField.vue +17 -8
  172. package/src/components/tree/TreeEditor.vue +3 -2
  173. package/src/components/view/View.vue +80 -207
  174. package/src/directives/appendToBody.js +1 -0
  175. package/src/helpers/validators.js +9 -35
  176. package/src/helpers/validators.spec.js +11 -48
  177. package/src/locales/en.js +4 -6
  178. package/src/locales/pl.js +158 -0
  179. package/src/locales/uk.js +6 -7
  180. package/src/components/icon/components/nomi-calendar-view.vue +0 -4
  181. package/src/components/icon/components/nomi-kanban-view.vue +0 -6
  182. package/src/components/icon/components/nomi-list-view.vue +0 -7
  183. package/src/components/icon/components/nomi-table-config.vue +0 -9
  184. package/src/components/icon/new-icons/calendar-view.svg +0 -3
  185. package/src/components/icon/new-icons/kanban-view.svg +0 -5
  186. package/src/components/icon/new-icons/list-view.svg +0 -6
  187. package/src/components/icon/new-icons/table-config.svg +0 -8
@@ -10,6 +10,7 @@
10
10
  <template v-for="(panel, n) of panelsStack">
11
11
  <panel
12
12
  :key="n"
13
+ :ref="`panel-${panel.id}`"
13
14
  :index="n"
14
15
  :panel="panel"
15
16
  :title="panel.title"
@@ -17,14 +18,22 @@
17
18
  :icon="panel.icon"
18
19
  :payload="panel.payload"
19
20
  :expandable="panelsStack.length > 1"
21
+ :isFullSize="isFullSize"
20
22
  :collapsed="panel.isCollapsed"
21
23
  :closeable="panel.isCloseable"
22
24
  :animate="panel.isAnimate"
23
25
  @open="openPanel($event[0], $event[1], n + 1)"
24
26
  @expand="expandPanel(panel)"
25
27
  @fullsize="fullsizePanel(panel)"
28
+ @collapse="collapsePanel(panel)"
26
29
  @close="closePanel(panel)"
30
+ @open-menu="$emit('open-menu', panel.type, panel.payload)"
31
+ @onboarding-progress="$emit('onboarding-progress', $event)"
32
+ @onboarding-step-viewed="$emit('onboarding-step-viewed', $event)"
27
33
  >
34
+ <template #before-header>
35
+ <slot name="before-header" :panel="panel" :index="n" :payload="panel.payload"></slot>
36
+ </template>
28
37
  <slot
29
38
  :name="panel.type"
30
39
  :panel="panel"
@@ -34,9 +43,9 @@
34
43
  :close="() => closePanel(panel)"
35
44
  :expand="() => expandPanel(panel)"
36
45
  :fullsize="() => fullsizePanel(panel)">
37
- <component :is="panels[panel.type].default || panels[panel.type]" :panel="panel" :payload="panel.payload" />
46
+ <component v-if="panel.components.default" :is="panel.components.default" :panel="panel" :payload="panel.payload" />
38
47
  </slot>
39
- <template v-if="$scopedSlots[`${panel.type}.title`] || panels[panel.type].title" #title>
48
+ <template v-if="$scopedSlots[`${panel.type}.title`] || panel.components.title" #title>
40
49
  <slot
41
50
  :name="`${panel.type}.title`"
42
51
  :panel="panel"
@@ -46,10 +55,10 @@
46
55
  :close="() => closePanel(panel)"
47
56
  :expand="() => expandPanel(panel)"
48
57
  :fullsize="() => fullsizePanel(panel)">
49
- <component v-if="panels[panel.type].title" :is="panels[panel.type].title" :panel="panel" :payload="panel.payload" />
58
+ <component v-if="panel.components.title" :is="panel.components.title" :panel="panel" :payload="panel.payload" />
50
59
  </slot>
51
60
  </template>
52
- <template v-if="$scopedSlots[`${panel.type}.buttons`] || panels[panel.type].buttons" #buttons>
61
+ <template v-if="$scopedSlots[`${panel.type}.buttons`] || panel.components.buttons" #buttons>
53
62
  <slot
54
63
  :name="`${panel.type}.buttons`"
55
64
  :panel="panel"
@@ -59,10 +68,10 @@
59
68
  :close="() => closePanel(panel)"
60
69
  :expand="() => expandPanel(panel)"
61
70
  :fullsize="() => fullsizePanel(panel)">
62
- <component v-if="panels[panel.type].buttons" :is="panels[panel.type].buttons" :panel="panel" :payload="panel.payload" />
71
+ <component v-if="panel.components.buttons" :is="panel.components.buttons" :panel="panel" :payload="panel.payload" />
63
72
  </slot>
64
73
  </template>
65
- <template v-if="$scopedSlots[`${panel.type}.header`] || panels[panel.type].header" #header>
74
+ <template v-if="$scopedSlots[`${panel.type}.header`] || panel.components.header" #header>
66
75
  <slot
67
76
  :name="`${panel.type}.header`"
68
77
  :panel="panel"
@@ -72,7 +81,7 @@
72
81
  :close="() => closePanel(panel)"
73
82
  :expand="() => expandPanel(panel)"
74
83
  :fullsize="() => fullsizePanel(panel)">
75
- <component v-if="panels[panel.type].header" :is="panels[panel.type].header" :panel="panel" :payload="panel.payload" />
84
+ <component v-if="panel.components.header" :is="panel.components.header" :panel="panel" :payload="panel.payload" />
76
85
  </slot>
77
86
  </template>
78
87
  </panel>
@@ -148,7 +157,6 @@ $double-an-time: $an-time * 2;
148
157
  //transition: opacity $an-time linear;
149
158
  }
150
159
  }
151
-
152
160
  //.slide-enter-active > div {
153
161
  // opacity: 0;
154
162
  //}
@@ -158,9 +166,11 @@ $double-an-time: $an-time * 2;
158
166
  //}
159
167
  </style>
160
168
  <script lang="ts">
161
- import { Vue, Component, Prop } from 'vue-property-decorator';
162
- import Panel from './Panel';
163
- import {hashToStack, stackToHash} from "./helpers";
169
+ import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
170
+ import itfIcon from '../icon/Icon.vue';
171
+ import Panel from './Panel.vue';
172
+ import {hashToStack, stackToHash} from "@itfin/components/src/components/panels/helpers";
173
+ import {emitGlobalEvent, setRootPanelList} from "@itfin/components/src/components/panels";
164
174
 
165
175
  interface VisualOptions {
166
176
  title: string;
@@ -175,6 +185,7 @@ export interface IPanel {
175
185
  payload: any;
176
186
  nocard?: boolean;
177
187
  isCollapsed: boolean;
188
+ isExpanded: boolean;
178
189
  isCloseable: boolean;
179
190
  isAnimate: boolean;
180
191
  open: (type: string, visOptions: VisualOptions, payload: any) => void;
@@ -195,6 +206,7 @@ export interface IPanel {
195
206
 
196
207
  @Component({
197
208
  components: {
209
+ itfIcon,
198
210
  Panel
199
211
  },
200
212
  directives: {
@@ -208,12 +220,27 @@ export interface IPanel {
208
220
  export default class PanelList extends Vue {
209
221
  @Prop() firstPanel: IPanel;
210
222
  @Prop() panels: Record<string, Component>;
223
+ @Prop({ default: () => {} }) searchPanel: (type: string) => boolean;
224
+ @Prop({ type: String, default: 'path' }) routeType: string;
225
+ @Prop({ type: String, default: '' }) routePrefix: string;
211
226
 
212
227
  panelsStack:IPanel[] = [];
213
228
 
214
229
  nextId:number = 0;
215
230
 
231
+ @Watch('panels', { deep: true })
232
+ onPanelsPropChanged(newPanels: Record<string, Component>) {
233
+ this.panelsStack.forEach(stackedPanel => {
234
+ const newPanel = newPanels[stackedPanel.type];
235
+ if (newPanel && typeof newPanel.inAppOnboarding === 'function') {
236
+ const updatedOnboarding = newPanel.inAppOnboarding(this.$t.bind(this));
237
+ stackedPanel.inAppOnboarding = updatedOnboarding;
238
+ }
239
+ });
240
+ }
241
+
216
242
  created() {
243
+ setRootPanelList(this);
217
244
  if (this.firstPanel) {
218
245
  this.internalOpenPanel(this.firstPanel.type, this.firstPanel.payload);
219
246
  }
@@ -233,6 +260,7 @@ export default class PanelList extends Vue {
233
260
  const newStack = [...this.panelsStack];
234
261
  const index = newStack.findIndex(p => p.id === panel.id);
235
262
  newStack[index].isCollapsed = false;
263
+ newStack[index].isExpanded = true;
236
264
  this.panelsStack = newStack;
237
265
  this.ensureOnlyTwoOpenPanels(panel.id);
238
266
  this.setPanelHash();
@@ -254,10 +282,12 @@ export default class PanelList extends Vue {
254
282
  keepOpenIds.push(panel2.id);
255
283
  }
256
284
  if (keepOpenIds.length === 1) {
257
- if (newStack.length - 1 === indexKeep) {
258
- keepOpenIds.push(newStack[indexKeep - 1].id);
259
- } else {
260
- keepOpenIds.push(newStack[indexKeep + 1].id);
285
+ if (!openPanels.find(p => p.id === keepOpenId).isExpanded) {
286
+ if (newStack.length - 1 === indexKeep) {
287
+ keepOpenIds.push(newStack[indexKeep - 1].id);
288
+ } else {
289
+ keepOpenIds.push(newStack[indexKeep + 1].id);
290
+ }
261
291
  }
262
292
  }
263
293
  for (const panel of newStack) {
@@ -267,29 +297,42 @@ export default class PanelList extends Vue {
267
297
  this.panelsStack = newStack;
268
298
  }
269
299
 
270
- internalOpenPanel(type: string, payload: any = {}, openIndex?: number, noEvents = false) {
271
- if (!this.panels[type]) {
272
- return;
300
+ async internalOpenPanel(type: string, payload: any = {}, openIndex?: number, noEvents = false, noExpand = false) {
301
+ let panel = this.panels[type];
302
+ if (!panel) {
303
+ panel = await this.searchPanel(type, this.panels);
304
+ if (!panel) {
305
+ console.error(`Panel type "${type}" not found`);
306
+ return;
307
+ }
308
+ panel.type = type;
273
309
  }
274
- if (typeof this.panels[type].caption !== 'function') {
310
+ if (typeof panel.caption !== 'function') {
275
311
  throw new Error('Panel component must have a "caption" function');
276
312
  }
277
313
  const newPanel:any = {
278
314
  id: this.nextId++,
279
- nocard: this.panels[type].nocard,
280
- title: this.panels[type].caption(this.$t.bind(this), payload),
281
- icon: this.panels[type].icon ? this.panels[type].icon(this.$t.bind(this), payload) : null,
315
+ nocard: panel.nocard,
316
+ title: panel.caption(this.$t.bind(this), payload),
317
+ icon: panel.icon ? panel.icon(this.$t.bind(this), payload) : null,
318
+ components: {
319
+ default: panel.default ?? undefined,
320
+ buttons: panel.buttons ?? undefined,
321
+ header: panel.header ?? undefined,
322
+ title: panel.title ?? undefined,
323
+ },
282
324
  type,
283
325
  payload,
284
326
  isCollapsed: false,
285
327
  isCloseable: true,
286
328
  __events: {},
329
+ inAppOnboarding: panel.inAppOnboarding ? panel.inAppOnboarding(this.$t.bind(this)) : null,
287
330
  };
288
331
  if (!this.panelsStack.length || openIndex === 0) {
289
332
  newPanel.isCloseable = false;
290
333
  }
291
334
  let newStack = [...this.panelsStack];
292
- if (this.panels[type].permanentExpanded && newStack.length) {
335
+ if (panel.permanentExpanded && newStack.length) {
293
336
  for (const panel of newStack) {
294
337
  panel.isCollapsed = true;
295
338
  }
@@ -299,25 +342,32 @@ export default class PanelList extends Vue {
299
342
  isAnimation = newStack.length === openIndex;
300
343
  newStack = newStack.slice(0, openIndex);
301
344
  }
345
+ if (newStack.length > 0 && !newStack.find(p => !p.isCollapsed) && !noExpand) {
346
+ // якщо немає відкритих панелей, то перша панель має бути розгорнута
347
+ newStack[0].isCollapsed = false;
348
+ }
302
349
  this.panelsStack = newStack;
303
350
  return new Promise(res => {
304
351
  this.$nextTick(() => { // щоб панелі змінювались при редагуванні
305
352
  const n = newStack.length;
306
353
  newPanel.isAnimate = isAnimation;
307
- newPanel.permanentExpanded = !!this.panels[type].permanentExpanded;
354
+ newPanel.permanentExpanded = !!panel.permanentExpanded;
308
355
  newPanel.emit = (event, ...args) => this.emitEvent(event, ...args);
309
- newPanel.open = (type, payload) => this.openPanel(type, payload, n + 1);
356
+ newPanel.open = (type, payload, index?:number) => this.openPanel(type, payload, index ?? n + 1);
310
357
  newPanel.close = () => this.closePanel(newPanel);
311
358
  newPanel.expand = () => this.expandPanel(newPanel);
312
359
  newPanel.getTitle = () => newPanel.title;
313
360
  newPanel.getIcon = () => newPanel.icon;
314
- newPanel.setTitle = (title: string) => { newPanel.title = title; };
361
+ newPanel.setTitle = (title: string) => { newPanel.title = title; this.updateTitle() };
315
362
  newPanel.setIcon = (icon: string) => { newPanel.icon = icon; };
316
- newPanel.on = (eventName, func: (event: string, ...args: any[]) => any) => {
317
- if (!newPanel.__events[eventName]) {
318
- newPanel.__events[eventName] = [];
363
+ newPanel.on = (eventName: string|string[], func: (event: string, ...args: any[]) => any) => {
364
+ const eventNames = Array.isArray(eventName) ? eventName : [eventName];
365
+ for (const evName of eventNames) {
366
+ if (!newPanel.__events[evName]) {
367
+ newPanel.__events[evName] = [];
368
+ }
369
+ newPanel.__events[evName].push(func);
319
370
  }
320
- newPanel.__events[eventName].push(func);
321
371
  };
322
372
  newPanel.off = (eventName, func: (event: string, ...args: any[]) => any) => {
323
373
  if (newPanel.__events[eventName]) {
@@ -337,9 +387,11 @@ export default class PanelList extends Vue {
337
387
  newPanel.getPayload = () => newPanel.payload;
338
388
  newPanel.setPayload = (value: any) => {
339
389
  newPanel.payload = value;
340
- newPanel.title = this.panels[type].caption(this.$t.bind(this), value);
341
- newPanel.icon = this.panels[type].icon ? this.panels[type].icon(this.$t.bind(this), payload) : null,
342
- this.setPanelHash()
390
+ this.setPanelHash();
391
+ }
392
+ newPanel.rebuildAllOnboardingPopovers = (refreshDelay?: number) => this.rebuildAllOnboardingPopovers(refreshDelay);
393
+ newPanel.rebuildPanelOnboardingPopovers = (panelId: string | number, refreshDelay?: number) => {
394
+ this.rebuildPanelOnboardingPopovers(panelId, refreshDelay);
343
395
  }
344
396
  newStack.push(newPanel);
345
397
  this.panelsStack = newStack;
@@ -353,12 +405,20 @@ export default class PanelList extends Vue {
353
405
  });
354
406
  }
355
407
 
408
+ updateTitle() {
409
+ const titles = this.panelsStack.map(p => p.getTitle()).filter(Boolean).reverse();
410
+ this.$root.$options.head.titleChunk = titles.join(' / ');
411
+ this.$meta().refresh();
412
+ }
413
+
356
414
  async openPanel(type: string, payload: any, openIndex?: number) {
357
415
  await this.internalOpenPanel(type, payload, openIndex);
358
- this.setPanelHash()
416
+ this.setPanelHash();
417
+ if(openIndex) this.$nextTick(() => { this.rebuildAllOnboardingPopovers() });
359
418
  }
360
419
 
361
420
  emitEvent(event: string, ...args: any[]) {
421
+ emitGlobalEvent(event, ...args);
362
422
  for (const panel of this.panelsStack) {
363
423
  if (panel.__events[event]) {
364
424
  for (const func of panel.__events[event]) {
@@ -376,22 +436,63 @@ export default class PanelList extends Vue {
376
436
  openPanel.isCollapsed = false;
377
437
  }
378
438
  const openPanelIndex = this.panelsStack.findIndex(p => p === openPanel);
379
- if (openPanelIndex > 0 && !openPanel?.permanentExpanded) {
439
+ if (openPanelIndex > 0 && !openPanel?.permanentExpanded && !openPanel.isExpanded) {
380
440
  this.panelsStack[openPanelIndex - 1].isCollapsed = false;
381
441
  }
382
442
  this.ensureOnlyTwoOpenPanels(openPanel.id);
383
443
  this.setPanelHash();
384
444
  this.emitEvent('panels.closed', panel);
385
445
  this.emitEvent('panels.changed', this.panelsStack);
446
+ this.$nextTick(() => { this.rebuildAllOnboardingPopovers() });
386
447
  }
387
448
 
388
449
  fullsizePanel(panel: IPanel) {
389
450
  const newStack = [...this.panelsStack];
390
451
  for (const p of newStack) {
452
+ p.isLastOpened = !p.isCollapsed && p !== panel;
391
453
  p.isCollapsed = p !== panel;
454
+ if (!p.isCollapsed) {
455
+ p.isExpanded = true;
456
+ }
457
+ }
458
+ this.panelsStack = newStack;
459
+ this.setPanelHash();
460
+ this.$nextTick(() => { this.rebuildAllOnboardingPopovers() });
461
+ }
462
+
463
+ get isFullSize() {
464
+ return this.panelsStack.filter(p => !p.isCollapsed).length === 1;
465
+ }
466
+
467
+ expandPanel(panel: IPanel) {
468
+ const newStack = [...this.panelsStack];
469
+ const index = newStack.findIndex(p => p.id === panel.id);
470
+ newStack[index].isCollapsed = false;
471
+ for (const p of newStack) {
472
+ p.isExpanded = false;
473
+ }
474
+ this.panelsStack = newStack;
475
+ this.ensureOnlyTwoOpenPanels(panel.id);
476
+ this.setPanelHash();
477
+ this.$nextTick(() => { this.rebuildAllOnboardingPopovers() });
478
+ }
479
+
480
+ collapsePanel(panel: IPanel) {
481
+ const newStack = [...this.panelsStack];
482
+ const currenctIndex = newStack.findIndex(p => p.id === panel.id);
483
+ const lastOpenedIndex = newStack.findIndex(p => p.isLastOpened);
484
+ if (lastOpenedIndex !== -1) { // якщо зебрежена остання відкрита панель
485
+ newStack[lastOpenedIndex].isCollapsed = false;
486
+ } else if (newStack[currenctIndex-1]) { // якщо після оновлення сторінки відсутнє значення "остання відкрита", то відкриваємо ту, що зліва
487
+ newStack[currenctIndex-1].isCollapsed = false;
488
+ }
489
+ for (const p of newStack) {
490
+ p.isExpanded = false;
392
491
  }
393
492
  this.panelsStack = newStack;
493
+ this.ensureOnlyTwoOpenPanels(panel.id);
394
494
  this.setPanelHash();
495
+ this.$nextTick(() => { this.rebuildAllOnboardingPopovers() });
395
496
  }
396
497
 
397
498
  getPanels(type) {
@@ -403,14 +504,19 @@ export default class PanelList extends Vue {
403
504
  }
404
505
 
405
506
  setPanelHash() {
406
- const hash = stackToHash(this.panelsStack).replace(/^#/, '');
407
- this.$router.push({ hash });
507
+ const hash = stackToHash(this.panelsStack, this.routePrefix).replace(/^#/, '');
508
+ if (this.routeType === 'path') {
509
+ this.$router.push({ path: `${hash}` });
510
+ } else {
511
+ this.$router.push({ hash });
512
+ }
513
+ this.updateTitle();
408
514
  }
409
515
 
410
516
  async parsePanelHash() {
411
- const {hash} = location;
517
+ const hash = this.routeType === 'path' ? location.pathname : location.hash;
412
518
  if (hash) {
413
- const panels = hashToStack(hash);
519
+ const panels = hashToStack(hash, this.routePrefix);
414
520
  const newStack = [];
415
521
  this.panelsStack = [];
416
522
  for (const panelIndex in panels) {
@@ -419,18 +525,25 @@ export default class PanelList extends Vue {
419
525
  // reuse panel
420
526
  this.panelsStack[panelIndex].payload = panel.payload;
421
527
  this.panelsStack[panelIndex].isCollapsed = panel.isCollapsed;
528
+ this.panelsStack[panelIndex].isExpanded = false;
422
529
  newStack.push(this.panelsStack[panelIndex]);
423
530
  } else {
424
- const resPanel = await this.internalOpenPanel(panel.type, panel.payload, undefined, true);
531
+ const resPanel = await this.internalOpenPanel(panel.type, panel.payload, undefined, true, true);
425
532
  if (resPanel) {
426
533
  resPanel.isCollapsed = panel.isCollapsed;
534
+ resPanel.isExpanded = false;
427
535
  resPanel.isAnimate = false;
428
536
  newStack.push(resPanel);
429
537
  }
430
538
  }
431
539
  }
540
+ const hasExpanded = newStack.length > 1 && newStack.filter(p => !p.isCollapsed).length === 1;
541
+ if (hasExpanded) {
542
+ newStack[newStack.findIndex(p => !p.isCollapsed)].isExpanded = true;
543
+ }
432
544
  this.panelsStack = newStack;
433
545
  this.emitEvent('panels.changed', this.panelsStack);
546
+ this.updateTitle();
434
547
  }
435
548
  }
436
549
 
@@ -452,5 +565,16 @@ export default class PanelList extends Vue {
452
565
  element.classList.add('animate');
453
566
  }
454
567
  }
568
+
569
+ rebuildPanelOnboardingPopovers(panelId: string | number, refreshDelay?: number) {
570
+ const panelRef = this.$refs[`panel-${panelId}`];
571
+ if (panelRef?.[0]) panelRef[0].rebuildOnboardingPopovers(refreshDelay);
572
+ }
573
+
574
+ rebuildAllOnboardingPopovers(refreshDelay?: number) {
575
+ this.panelsStack.forEach((panel) => {
576
+ this.rebuildPanelOnboardingPopovers(panel.id, refreshDelay);
577
+ });
578
+ }
455
579
  }
456
580
  </script>
@@ -1,33 +1,61 @@
1
+ import JSON5 from 'json5'
2
+ import {isPathType} from "./index";
3
+
1
4
  export interface IPanel {
2
5
  type: string;
3
6
  payload?: any;
4
7
  isCollapsed?: boolean;
5
8
  }
6
9
 
7
- export function stackToHash(stack: IPanel[]) {
10
+ const COLLAPSE_SYMBOL = '~'
11
+ const PARAMS_SYMBOL = ';'
12
+
13
+ export function stackToHash(stack: IPanel[], routePrefix: string = '') {
8
14
  const hash = stack.map(panel => {
9
- return `${panel.type}${panel.isCollapsed ? '' : '!'}=${JSON.stringify(panel.payload || {})}`;
10
- }).join('&');
11
- return `#${hash}`;
15
+ let json = JSON5.stringify(panel.payload || {});
16
+ json = json.substring(1, json.length - 1); // Remove the outer {}
17
+ return `${panel.type}${panel.isCollapsed ? COLLAPSE_SYMBOL : ''}${json ? PARAMS_SYMBOL : ''}${json}`;
18
+ }).join(isPathType() ? '/' : '&');
19
+ let url = hash;
20
+ if (routePrefix) {
21
+ url = `${routePrefix}/` + url;
22
+ }
23
+ return isPathType() ? `/${url}` : `#${url}`;
12
24
  }
13
25
 
14
26
 
15
- export function hashToStack(hash: string|undefined): IPanel[] {
27
+ export function hashToStack(hash: string|undefined, routePrefix: string = ''): IPanel[] {
16
28
  let stack:IPanel[] = [];
17
- if (hash) {
18
- const str = hash.replace(/^#/, '');
19
-
20
- stack = str.split('&').map(item => {
21
- const [type, payload] = item.split('=');
22
- const isCollapsed = !type.includes('!');
29
+ let str = hash.replace(isPathType() ? /^\// : /^#/, '');
30
+ if (routePrefix) {
31
+ const prefix = isPathType() ? `${routePrefix}/` : `${routePrefix}`;
32
+ if (str.startsWith(prefix)) {
33
+ str = str.substring(prefix.length);
34
+ }
35
+ }
36
+ if (str) {
37
+ stack = str.split(isPathType() ? '/' : '&').map(item => {
38
+ if (!item.includes(PARAMS_SYMBOL)) {
39
+ return { type: item.replace(COLLAPSE_SYMBOL, ''), isCollapsed: item.includes(COLLAPSE_SYMBOL), payload: {} };
40
+ }
41
+ const [type, payload] = item.split(PARAMS_SYMBOL);
42
+ const isCollapsed = type.includes(COLLAPSE_SYMBOL);
23
43
  let payloadObj:any = {};
24
44
  try {
25
- payloadObj = JSON.parse(decodeURIComponent(payload));
45
+ let json = decodeURIComponent(payload);
46
+ if (!json.startsWith('{')) {
47
+ json = `{${json}`; // Ensure it starts with a '{' to be valid JSON
48
+ }
49
+ if (!json.endsWith('}')) {
50
+ json += '}'; // Ensure it ends with a '}' to be valid JSON
51
+ }
52
+ payloadObj = JSON5.parse(json);
26
53
  } catch (e) {
27
54
  // ignore
55
+ console.warn(`Error parsing payload for type ${type}:`, payload, e);
28
56
  }
29
57
  return {
30
- type: type.replace('!', ''),
58
+ type: type.replace(COLLAPSE_SYMBOL, ''),
31
59
  isCollapsed,
32
60
  payload: payloadObj
33
61
  };