@mixd-id/web-scaffold 0.2.240702 → 0.2.240704

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 (95) hide show
  1. package/docs/schema/user-action.json +266 -0
  2. package/package.json +6 -2
  3. package/public/assets/dashboard/bar.png +0 -0
  4. package/public/assets/dashboard/doughnut.png +0 -0
  5. package/public/assets/dashboard/metric.png +0 -0
  6. package/public/assets/dashboard/pie.png +0 -0
  7. package/public/assets/dashboard/polar-area.png +0 -0
  8. package/public/assets/dashboard/virtual-table.png +0 -0
  9. package/public/static/dashboard/bar.png +0 -0
  10. package/public/static/dashboard/doughnut.png +0 -0
  11. package/public/static/dashboard/metric.png +0 -0
  12. package/public/static/dashboard/pie.png +0 -0
  13. package/public/static/dashboard/polar-area.png +0 -0
  14. package/public/static/dashboard/virtual-table.png +0 -0
  15. package/src/components/Button.vue +179 -160
  16. package/src/components/Checkbox.vue +0 -1
  17. package/src/components/Datepicker.vue +8 -6
  18. package/src/components/GHeatMaps.vue +317 -0
  19. package/src/components/GmapsDirection.vue +191 -0
  20. package/src/components/Grid.vue +2 -0
  21. package/src/components/HTMLEditor.vue +2 -2
  22. package/src/components/List.vue +384 -308
  23. package/src/components/Modal.vue +2 -3
  24. package/src/components/PresetSelectorFilterItem.vue +15 -2
  25. package/src/components/Switch.vue +3 -0
  26. package/src/components/Tabs.vue +1 -1
  27. package/src/components/TextWithTag.vue +67 -25
  28. package/src/components/Textbox.vue +5 -0
  29. package/src/components/VirtualGrid.vue +224 -228
  30. package/src/components/VirtualTable.vue +46 -28
  31. package/src/configs/dashboard/bar.js +10 -0
  32. package/src/configs/dashboard/collection-1.js +5 -0
  33. package/src/configs/dashboard/doughnut.js +7 -0
  34. package/src/configs/dashboard/gheatmaps.js +9 -0
  35. package/src/configs/dashboard/grid-2.js +34 -0
  36. package/src/configs/dashboard/grid-3.js +34 -0
  37. package/src/configs/dashboard/grid-4.js +34 -0
  38. package/src/configs/dashboard/grid.js +15 -0
  39. package/src/configs/dashboard/metric.js +10 -0
  40. package/src/configs/dashboard/pie.js +7 -0
  41. package/src/configs/dashboard/polar-area.js +7 -0
  42. package/src/configs/dashboard/virtual-table.js +9 -0
  43. package/src/defs/dashboard-preset.js +22 -0
  44. package/src/index.js +37 -23
  45. package/src/mixin/ready-state.js +37 -0
  46. package/src/stores/datasource.js +11 -0
  47. package/src/themes/default/index.js +1 -1
  48. package/src/utils/dashboard.js +1080 -0
  49. package/src/utils/event-bus.js +8 -0
  50. package/src/utils/helpers.js +56 -8
  51. package/src/utils/helpers.mjs +35 -1
  52. package/src/utils/preset-selector.js +5 -2
  53. package/src/utils/preset-selector.mjs +23 -13
  54. package/src/widgets/BotEditor/BotEditorActions.vue +162 -0
  55. package/src/widgets/BotEditor.vue +228 -0
  56. package/src/widgets/Dashboard/BarChart.vue +330 -0
  57. package/src/widgets/Dashboard/BarChartSetting.vue +317 -0
  58. package/src/widgets/Dashboard/DatasourceFilterSharing.vue +93 -0
  59. package/src/widgets/Dashboard/DatasourcePreview.vue +93 -0
  60. package/src/widgets/Dashboard/DatasourceSelector.vue +122 -0
  61. package/src/widgets/Dashboard/Doughnut.vue +157 -0
  62. package/src/widgets/Dashboard/DoughnutSetting.vue +196 -0
  63. package/src/widgets/Dashboard/GHeatMapsSetting.vue +108 -0
  64. package/src/widgets/Dashboard/InteractionEdit.vue +228 -0
  65. package/src/widgets/Dashboard/Metric.vue +76 -0
  66. package/src/widgets/Dashboard/MetricSetting.vue +174 -0
  67. package/src/widgets/Dashboard/Pie.vue +139 -0
  68. package/src/widgets/Dashboard/PieSetting.vue +247 -0
  69. package/src/widgets/Dashboard/PolarArea.vue +159 -0
  70. package/src/widgets/Dashboard/PolarAreaSetting.vue +195 -0
  71. package/src/widgets/Dashboard/SharingModal.vue +116 -0
  72. package/src/widgets/Dashboard/ViewSelector.vue +183 -0
  73. package/src/widgets/Dashboard/VirtualColumnEdit.vue +97 -0
  74. package/src/widgets/Dashboard/VirtualTableSetting.vue +234 -0
  75. package/src/widgets/Dashboard.vue +1773 -0
  76. package/src/widgets/PresetBar.vue +136 -175
  77. package/src/widgets/PresetBarPivot.vue +186 -0
  78. package/src/widgets/PresetSelector.vue +2 -2
  79. package/src/widgets/UserActionBuilder/UserActionCondition.vue +99 -0
  80. package/src/widgets/UserActionBuilder/UserActionConsole.vue +77 -0
  81. package/src/widgets/UserActionBuilder/UserActionItem.vue +166 -58
  82. package/src/widgets/UserActionBuilder/UserActionOutput.vue +35 -9
  83. package/src/widgets/UserActionBuilder/UserActionOutputDelay.vue +27 -0
  84. package/src/widgets/UserActionBuilder/UserActionOutputLog.vue +28 -0
  85. package/src/widgets/UserActionBuilder/UserActionOutputReply.vue +135 -0
  86. package/src/widgets/UserActionBuilder/UserActionProps.vue +213 -0
  87. package/src/widgets/UserActionBuilder.vue +68 -199
  88. package/src/widgets/WebPageBuilder4/GridSetting.vue +123 -73
  89. package/src/widgets/WebPageBuilder4/HeightSetting.vue +14 -11
  90. package/src/widgets/WebPageBuilder4/MarginSetting.vue +4 -1
  91. package/src/widgets/WebPageBuilder4/MultiValueSetting.vue +12 -4
  92. package/src/widgets/WebPageBuilder4/PaddingSetting.vue +4 -0
  93. package/src/widgets/WebPageBuilder4/TreeView.vue +6 -3
  94. package/src/widgets/WebPageBuilder4/TreeViewItem.vue +32 -58
  95. package/tailwind.config.js +2 -2
@@ -8,9 +8,17 @@
8
8
  class="relative flex flex-col border-r-[1px] border-text-50 panel-400"
9
9
  :style="sidebarStyle">
10
10
 
11
- <PresetBar :config="config"
11
+ <PresetBar :config="cConfig"
12
+ :presets="presets"
13
+ :shared-presets="sharedPresets"
12
14
  class="flex-1"
15
+ :can-share="!!sharingSrc"
16
+ :sharing-src="sharingSrc"
17
+ :remove-sharing-src="removeSharingSrc"
13
18
  :enumCache="enumCache"
19
+ @duplicate="duplicate"
20
+ @remove="removePreset"
21
+ @remove-shared="removeShared"
14
22
  @apply="load">
15
23
 
16
24
  <template #toolbar>
@@ -26,7 +34,7 @@
26
34
  @mousedown="(e) => $util.dragResize(e, resize1)"></div>
27
35
  </div>
28
36
 
29
- <div class="flex-1 flex flex-col">
37
+ <div class="flex-1 flex flex-col" v-if="preset">
30
38
 
31
39
  <div class="flex-1 flex flex-col" :class="containerClass">
32
40
  <slot name="head"
@@ -44,7 +52,7 @@
44
52
  <small class="text-text-400 text-xs">{{ title ?? 'Untitled' }}</small>
45
53
  <div class="flex flex-row items-baseline gap-1">
46
54
  <h5 class="whitespace-nowrap relative top-[-2px] text-ellipsis overflow-hidden group-hover:text-primary">
47
- {{ preset.name ?? 'Preset Name' }}
55
+ {{ preset.params.name ?? 'Preset Name' }}
48
56
  </h5>
49
57
  <svg width="13" height="13" class="fill-text group-hover:fill-primary relative top-[-1px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M310.6 246.6l-127.1 128C176.4 380.9 168.2 384 160 384s-16.38-3.125-22.63-9.375l-127.1-128C.2244 237.5-2.516 223.7 2.438 211.8S19.07 192 32 192h255.1c12.94 0 24.62 7.781 29.58 19.75S319.8 237.5 310.6 246.6z"/></svg>
50
58
  </div>
@@ -53,13 +61,13 @@
53
61
  <div class="flex flex-col min-w-[300px] divide-y divide-text-50">
54
62
 
55
63
  <div class="p-3 flex flex-col gap-1 bg-base-300">
56
- <button v-for="(preset, idx) in config.presets"
64
+ <button v-for="(preset, idx) in presets"
57
65
  class="p-2 px-5 text-left flex flex-row items-center rounded-md"
58
- :class="config.presetIdx === idx ? 'bg-primary-200 text-white' : 'hover:bg-primary-100'"
66
+ :class="cConfig.params.presetIdx === preset.uid ? 'bg-primary-200 text-white' : 'hover:bg-primary-100'"
59
67
  type="button"
60
- @click="selectPreset(idx)">
68
+ @click="selectPreset(preset.uid)">
61
69
  <label class="flex-1 pr-12">
62
- {{ preset.name }}
70
+ {{ preset.params.name }}
63
71
  </label>
64
72
  <div class="p-1">
65
73
  <div v-if="idx < 10"
@@ -120,57 +128,77 @@
120
128
  </div>
121
129
  </slot>
122
130
 
123
- <div v-if="readyState === 2" class="flex-1 flex items-center justify-center">
124
- <svg class="animate-spin" width="36" height="36" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
125
- </div>
131
+ <div class="flex-1 flex" ref="content">
132
+ <div v-if="readyState === 2" class="flex-1 flex items-center justify-center">
133
+ <svg class="animate-spin" width="36" height="36" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
134
+ </div>
126
135
 
127
- <div v-else-if="presetView === 'table' || pivotEnabled" class="flex-1 flex" :class="pivotEnabled ? 'p-3 panel-400 md:p-0' : ''">
128
- <VirtualTable
129
- ref="table"
130
- :columns="columns"
131
- class="flex-1 rounded-lg panel-400"
132
- :items="data.items"
133
- :enumCache="enumCache"
134
- @scroll-end="loadNext"
135
- @item-click="onTableItemClick">
136
-
137
- <template v-for="(_, slot) in headerSlots" #[slot]="{ item, index }">
138
- <div :class="getHeader(slot.replace('col-', ''))">
139
- <slot :name="slot" :item="item" :index="index"></slot>
140
- </div>
141
- </template>
136
+ <div v-else-if="presetView === 'table' || pivotEnabled" class="flex-1 flex" :class="pivotEnabled ? 'p-3 panel-400 md:p-0' : ''">
137
+ <VirtualTable
138
+ ref="table"
139
+ :columns="columns"
140
+ class="flex-1 rounded-lg panel-400"
141
+ :items="data.items"
142
+ :enumCache="enumCache"
143
+ @scroll-end="loadNext"
144
+ @item-click="onTableItemClick">
145
+
146
+ <template v-for="(_, slot) in headerSlots" #[slot]="{ item, index }">
147
+ <div :class="getHeader(slot.replace('col-', ''))">
148
+ <slot :name="slot" :item="item" :index="index"></slot>
149
+ </div>
150
+ </template>
142
151
 
143
- <template v-for="(_, slot) in contentSlots" #[slot]="{ item, index }">
144
- <slot :name="slot" :item="item" :index="index"></slot>
145
- </template>
152
+ <template v-for="(_, slot) in contentSlots" #[slot]="{ item, index }">
153
+ <slot :name="slot" :item="item" :index="index"></slot>
154
+ </template>
155
+
156
+ <template #end>
157
+ <div v-if="data?.hasNext" class="flex justify-center p-3">
158
+ <svg v-if="readyState === 4" class="animate-spin aspect-square w-[14px] h-[14px] fill-text-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
159
+ <button v-else type="button" class="text-text-300" @click="loadNext()">
160
+ {{ $t('Load More' )}}
161
+ </button>
162
+ </div>
163
+ </template>
146
164
 
147
- </VirtualTable>
148
- </div>
165
+ </VirtualTable>
166
+ </div>
149
167
 
150
- <VirtualGrid v-else-if="presetView === 'grid'"
151
- ref="grid"
152
- :items="dataItems"
153
- :column="computedGridColumn"
154
- :class="gridClass"
155
- @scroll-end="loadNext"
156
- :container-class="`${gridContainerClass}`"
157
- :config="config">
158
- <template #item="{ item }">
159
- <slot name="gridItem" :item="item" :enumCache="enumCache" :getEnumText="getEnumText">
160
- <div class="flex flex-row panel-400 rounded-lg overflow-hidden md:rounded-lg overflow-hidden p-3 gap-3">
161
- <div>
162
- <Image :src="item.imageUrl" class="bg-text-50 w-[64px] h-[64px] rounded-lg" />
163
- </div>
164
- <div class="flex-1 flex flex-col leading-tight">
165
- <small>{{ item.code }}</small>
166
- <strong>{{ item.name }}</strong>
168
+ <VirtualGrid v-else-if="presetView === 'grid'"
169
+ ref="grid"
170
+ :items="dataItems"
171
+ :column="computedGridColumn"
172
+ :class="gridClass"
173
+ @scroll-end="loadNext"
174
+ :container-class="`${gridContainerClass}`"
175
+ :config="config">
176
+ <template #item="{ item }">
177
+ <slot name="gridItem" :item="item" :enumCache="enumCache" :getEnumText="getEnumText">
178
+ <div class="flex flex-row panel-400 rounded-lg overflow-hidden md:rounded-lg overflow-hidden p-3 gap-3">
179
+ <div>
180
+ <Image :src="item.imageUrl" class="bg-text-50 w-[64px] h-[64px] rounded-lg" />
181
+ </div>
182
+ <div class="flex-1 flex flex-col leading-tight">
183
+ <small>{{ item.code }}</small>
184
+ <strong>{{ item.name }}</strong>
185
+ </div>
167
186
  </div>
187
+ </slot>
188
+ </template>
189
+
190
+ <template #end>
191
+ <div v-if="data?.hasNext" class="flex justify-center p-3">
192
+ <svg v-if="readyState === 4" class="animate-spin aspect-square w-[14px] h-[14px] fill-text-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
193
+ <button v-else type="button" class="text-text-300" @click="loadNext()">
194
+ {{ $t('Load More' )}}
195
+ </button>
168
196
  </div>
169
- </slot>
170
- </template>
171
- </VirtualGrid>
197
+ </template>
198
+ </VirtualGrid>
172
199
 
173
- <PresetSelector ref="presetSelector" :config="config" @select="load" />
200
+ <PresetSelector ref="presetSelector" :config="config" @select="load" />
201
+ </div>
174
202
  </div>
175
203
 
176
204
  <div v-if="extBar.open && pivotEnabled"
@@ -182,7 +210,7 @@
182
210
 
183
211
  <VirtualTable v-else-if="presetView === 'table'"
184
212
  ref="table"
185
- :columns="preset.columns"
213
+ :columns="preset.params.columns"
186
214
  class="flex-1 rounded-lg panel-400"
187
215
  :items="data.extItems"
188
216
  :enumCache="enumCache"
@@ -247,64 +275,167 @@
247
275
 
248
276
  import VirtualTable from "./VirtualTable.vue";
249
277
  import VirtualGrid from "./VirtualGrid.vue";
250
- import throttle from "lodash/throttle";
251
278
  import PresetSelector from "../widgets/PresetSelector.vue";
252
- import groupBy from "lodash/groupBy";
253
- import { generatePivotColumns, generateTotalColumns, sortsFn } from "../utils/preset-selector.mjs";
279
+ import {generatePivotColumns, generateTotalColumns, setupConfig, sortsFn} from "../utils/preset-selector.mjs";
254
280
  import PresetBar from "../widgets/PresetBar.vue";
281
+ import {invokeAfterIdle} from "../utils/helpers.mjs";
255
282
 
256
283
  export default{
257
284
 
258
- emits: [ 'after-load', 'open-preset', 'signal', 'pivot-item-click' ],
285
+ components: {PresetBar, PresetSelector, VirtualGrid, VirtualTable},
259
286
 
260
- inject: [ 'socket', 'toast' ],
287
+ computed: {
261
288
 
262
- props: {
263
- config: {
264
- type: Object,
265
- default: { columns:[], presets:[{}] }
289
+ columns(){
290
+ if((this.preset?.params.pivot ?? {}).enabled){
291
+ return this.preset.params.pivot.columns ?? []
292
+ }
293
+
294
+ for(let idx in this.preset?.columns){
295
+ if(this.preset.params.columns[idx].defaultFormat){
296
+ this.preset.params.columns[idx].format = this.preset.params.columns[idx].defaultFormat
297
+ delete this.preset.params.columns[idx].defaultFormat
298
+ }
299
+ }
300
+
301
+ return this.preset?.params.columns
266
302
  },
267
- presetKey: String,
268
- subscribeKey: String,
269
- src: undefined,
270
- title: String,
271
- view: {
272
- type: String,
273
- validator(value) {
274
- return ['grid', 'table'].includes(value)
303
+
304
+ contentSlots(){
305
+ const slots = {}
306
+ for(let key in this.$slots){
307
+ if(!key.startsWith('col-'))
308
+ slots[key] = this.$slots[key]
275
309
  }
310
+ return slots
276
311
  },
277
- gridColumn: [ Number, String ],
278
- gridClass: {
279
- type: String,
280
- default: "flex-1 md:p-3"
312
+
313
+ headerSlots(){
314
+ const slots = {}
315
+ for(let key in this.$slots){
316
+ if(key.startsWith('col-'))
317
+ slots[key] = this.$slots[key]
318
+ }
319
+ return slots
281
320
  },
282
- gridContainerClass: {
283
- type: String,
284
- default: "md:gap-5"
321
+
322
+ computedGridColumn(){
323
+ return this.$device.type === 'mobile' ? 1 :
324
+ (this.gridColumn ?? 3)
285
325
  },
286
- headerClass: String,
287
- presetMode: {
288
- type: String,
289
- default: "sidebar"
326
+
327
+ computedPresetMode(){
328
+ return this.$device.type === 'mobile' ? 'popup' : this.presetMode
290
329
  },
291
- containerClass: {
292
- type: String,
293
- default: "md:p-5 md:gap-3"
330
+
331
+ dataItems(){
332
+ if((this.preset.sorts ?? []).length > 0){
333
+ return (this.data.items ?? []).sort((a, b) => {
334
+ return sortsFn(a, b, this.preset.sorts, 0)
335
+ })
336
+ }
337
+ else{
338
+ return this.data.items ?? []
339
+ }
294
340
  },
295
- toolbar: {
296
- type: [ String, Boolean ],
297
- default: true
341
+
342
+ preset(){
343
+ return this.presets[0]
344
+ },
345
+
346
+ presets(){
347
+ return (this.cConfig?.presets ?? [])
348
+ },
349
+
350
+ presetView(){
351
+ if(!this.preset.view)
352
+ this.preset.view = 'table'
353
+
354
+ return this.$device.type === 'mobile' ? 'grid' :
355
+ (this.view ?? this.preset.view)
356
+ },
357
+
358
+ sidebar(){
359
+ if(!this.cConfig.sidebar || !('open' in this.cConfig.sidebar))
360
+ this.cConfig.sidebar = {
361
+ open: false,
362
+ width: 270
363
+ }
364
+
365
+ return this.cConfig.sidebar
366
+ },
367
+
368
+ sidebarStyle(){
369
+ return {
370
+ width: !this.sidebar.open ? 0 : this.sidebar.width + 'px'
371
+ }
372
+ },
373
+
374
+ extBar(){
375
+ if(!this.cConfig.extbar)
376
+ this.cConfig.extbar = {
377
+ open: false,
378
+ height: Math.round(window.innerHeight / 2.2)
379
+ }
380
+
381
+ return this.cConfig.extbar
382
+ },
383
+
384
+ extStyle(){
385
+ return {
386
+ height: this.extBar.height + "px"
387
+ }
388
+ },
389
+
390
+ pivotEnabled(){
391
+ return ((this.preset ?? {}).pivot ?? {}).enabled
392
+ },
393
+
394
+ cConfig(){
395
+ return this.config ?? this._config
396
+ }
397
+
398
+ },
399
+
400
+ data(){
401
+ return {
402
+ readyState: 0,
403
+ data: {
404
+ itemsPerPage: 16,
405
+ },
406
+ observer: null,
407
+ compPrefix: '',
408
+ enumCache: {},
409
+ extItems: null,
410
+ lastEnumItems: null,
411
+
412
+ _config: {}
298
413
  }
299
414
  },
300
415
 
416
+ emits: [ 'after-load', 'open-preset', 'signal', 'pivot-item-click' ],
417
+
418
+ inject: [ 'socket', 'toast' ],
419
+
301
420
  methods: {
302
421
 
422
+ calcItemsPerPage(){
423
+
424
+ if(this.$refs.content){
425
+ this.data.itemsPerPage = Math.round((this.$refs.content.clientHeight - 36) / 36) + 3
426
+ }
427
+
428
+ },
429
+
430
+ duplicate(){
431
+ this.saveConfig(false, true)
432
+ },
433
+
303
434
  getEnumText(key, value){
304
435
 
305
436
  let enumText
306
437
 
307
- const column = this.config.columns.find(_ => _.key === key)
438
+ const column = this.cConfig.columns.find(_ => _.key === key)
308
439
 
309
440
  if(Array.isArray(column.typeParams)){
310
441
  const typeParam = (column.typeParams ?? []).filter((_) => _.value === value).pop()
@@ -329,14 +460,14 @@ export default{
329
460
  },
330
461
 
331
462
  load(){
332
- if(this.src){
333
- if(!this.preset.columns){
334
- this.preset.columns = JSON.parse(JSON.stringify(this.config.columns))
463
+ if(this.src && this.preset){
464
+ if(!this.preset.params.columns){
465
+ this.preset.params.columns = JSON.parse(JSON.stringify(this.config.params.columns))
335
466
  }
336
467
 
337
468
  this.readyState = 2
338
469
  return this.socket.send(this.src, {
339
- ...this.preset,
470
+ ...this.preset.params,
340
471
  itemsPerPage: this.data.itemsPerPage,
341
472
  })
342
473
  .then(data => {
@@ -359,57 +490,26 @@ export default{
359
490
  ((this.data ?? {}).hasNext !== false) &&
360
491
  !(this.preset.pivot ?? {}).enabled) {
361
492
 
362
- //console.log('last id', this.dataItems[this.dataItems.length - 1].id)
363
-
493
+ this.readyState = 4
364
494
  this.socket.send(this.src, {
365
495
  ...this.preset,
366
496
  itemsPerPage: this.data.itemsPerPage,
367
497
  afterItem: this.data.items[this.data.items.length - 1],
368
498
  })
369
499
  .then(data => {
370
- //console.log('next id', data.items[0].id)
371
-
372
500
  this.data.items.push(...data.items)
373
501
  this.loadEnums(data.items)
374
502
  })
375
503
  .catch(err => this.toast(err))
504
+ .finally(_ => this.readyState = 1)
376
505
  }
377
506
  return new Promise(resolve => resolve())
378
507
  },
379
508
 
380
- loadPreset(){
381
- if(!Object.keys(this.$route.query).map(_ => _.toLowerCase()).includes('reset')){
382
- if(this.presetKey){
383
- return this.socket.send('user.preset', { key:this.presetKey })
384
- .then(config => {
385
- Object.assign(this.config, config)
386
- })
387
- }
388
- return new Promise(resolve => resolve())
389
- }
390
- else{
391
- return new Promise((resolve) => {
392
- this.savePreset()
393
-
394
- const query = {}
395
- for(let key in this.$route.query){
396
- if(key.toLowerCase() !== 'reset')
397
- query[key] = this.$route.query[key]
398
- }
399
- this.$router.replace({
400
- ...this.$route,
401
- query
402
- })
403
-
404
- resolve()
405
- })
406
- }
407
- },
408
-
409
509
  loadEnums(items){
410
510
 
411
- for(let i in this.preset.columns){
412
- const column = this.preset.columns[i]
511
+ for(let i in this.preset.params.columns){
512
+ const column = this.preset.params.columns[i]
413
513
 
414
514
  if(column.enumSrc) {
415
515
 
@@ -431,7 +531,6 @@ export default{
431
531
  columns: [
432
532
  { key:attr, visible:true }
433
533
  ],
434
- itemsPerPage: 1000,
435
534
  filters: [
436
535
  {
437
536
  key:key, value: [ { operator:'in', value:reqItems } ]
@@ -472,10 +571,10 @@ export default{
472
571
  ],
473
572
  pivot: null
474
573
  })
475
- .then((res) => {
476
- this.data.extItems = res.items
477
- })
478
- .finally(_ => this.readyState = 1)
574
+ .then((res) => {
575
+ this.data.extItems = res.items
576
+ })
577
+ .finally(_ => this.readyState = 1)
479
578
  },
480
579
 
481
580
  loadExtNext(){
@@ -497,26 +596,78 @@ export default{
497
596
  },
498
597
 
499
598
  resize1(w){
500
- if(this.config.sidebar.width + w >= 270 && this.config.sidebar.width + w <= 600){
501
- this.config.sidebar.width += w
599
+ if(this.cConfig.sidebar.width + w >= 270 && this.cConfig.sidebar.width + w <= 600){
600
+ this.cConfig.sidebar.width += w
502
601
  }
503
602
  },
504
603
 
505
- selectPreset(idx){
506
- if(this.config.presets[idx]){
507
- this.config.presetIdx = idx
604
+ selectPreset(uid){
605
+ const preset = this.presets.find(_ => _.uid === uid)
606
+
607
+ if(preset){
608
+ this.cConfig.presetIdx = uid
508
609
  this.load()
509
610
  this.$refs.presetSelector.close()
510
611
  this.$refs.contextMenu.close()
511
612
  }
512
613
  },
513
614
 
514
- savePreset: throttle(function() {
515
- if(this.presetKey) {
516
- this.socket.send('user.preset',
517
- {key: this.presetKey, config: this.config})
615
+ async loadDefaultConfig(save = false){
616
+ if(typeof this.defaultConfig !== 'function') return
617
+
618
+ this._config = setupConfig(JSON.parse(JSON.stringify((await this.defaultConfig()).default)))
619
+
620
+ this.saveConfig()
621
+ },
622
+
623
+ async loadConfig(){
624
+ if(!this.presetKey || !this.presetSrc) return
625
+
626
+ if(Object.keys(this.$route.query).map(_ => _.toLowerCase()).includes('reset')){
627
+ await this.loadDefaultConfig(true)
628
+
629
+ this.$router.replace({ query: {
630
+ ...this.$route.query,
631
+ reset: undefined
632
+ }})
633
+ }
634
+ else{
635
+ return this.socket.send(this.presetSrc, { key:this.presetKey })
636
+ .then(async (config) => {
637
+ this._config = config
638
+ })
639
+ .catch(() => {})
518
640
  }
519
- }, 1000, { leading:true }),
641
+ },
642
+
643
+ saveConfig: invokeAfterIdle(function(){
644
+ if(!this.presetKey || !this.presetSrc) return
645
+
646
+ return this.socket.send(this.presetSrc, {
647
+ key:this.presetKey,
648
+ config: this.cConfig
649
+ })
650
+ .catch(() => {})
651
+ }),
652
+
653
+ async removePreset(preset){
654
+ return this.socket.send(this.presetSrc, {
655
+ key: this.presetKey,
656
+ removePreset: preset
657
+ })
658
+ .catch(() => {})
659
+ },
660
+
661
+ async removeShared(uid){
662
+ return this.socket.send(this.presetSrc, {
663
+ key: this.presetKey,
664
+ removeShared: [ uid ]
665
+ })
666
+ .then(_ => {
667
+ this.sharedPresets.splice(this.sharedPresets.findIndex(_ => _.uid === uid), 1)
668
+ })
669
+ .catch(() => {})
670
+ },
520
671
 
521
672
  openPreset(){
522
673
 
@@ -567,12 +718,12 @@ export default{
567
718
  ...this.preset,
568
719
  [key]: items.map(item => item[key])
569
720
  })
570
- .then(({ items:nextItems }) => {
571
- nextItems.forEach(item => this.$util.unshift(this.data.items, item, { highlight: true }))
721
+ .then(({ items:nextItems }) => {
722
+ nextItems.forEach(item => this.$util.unshift(this.data.items, item, { highlight: true }))
572
723
 
573
- const destroyedItems = items.filter(_ => !nextItems.find(i => i[key] === _[key]))
574
- this.$util.remove(this.data.items, destroyedItems)
575
- })
724
+ const destroyedItems = items.filter(_ => !nextItems.find(i => i[key] === _[key]))
725
+ this.$util.remove(this.data.items, destroyedItems)
726
+ })
576
727
  break
577
728
  }
578
729
 
@@ -587,7 +738,7 @@ export default{
587
738
  for(let idx in this.preset.pivot.rows){
588
739
  const pivotRow = this.preset.pivot.rows[idx]
589
740
  const key = pivotRow.key
590
- const presetColumn = this.preset.columns.find(_ => _.key === key)
741
+ const presetColumn = this.preset.params.columns.find(_ => _.key === key)
591
742
  let value = item[pivotRow.key]
592
743
  switch(presetColumn.type){
593
744
 
@@ -669,10 +820,10 @@ export default{
669
820
  let [ kk, ...vv ] = column.key.split('-')
670
821
  vv = vv.join('-')
671
822
 
672
- if(![ 'count', 'countDistinct' ].includes(vv)){
823
+ if(![ 'count', 'countDistinct', 'sum' ].includes(vv)){
673
824
 
674
825
  const key = kk.substring(1)
675
- const presetColumn = this.preset.columns.find(_ => _.key === key)
826
+ const presetColumn = this.preset.params.columns.find(_ => _.key === key)
676
827
  let value = vv
677
828
  switch(presetColumn.type){
678
829
 
@@ -696,6 +847,17 @@ export default{
696
847
  }]
697
848
  })
698
849
  }
850
+ else if([ 'sum' ].includes(vv)){
851
+ const key = kk.substring(1)
852
+
853
+ filters.push({
854
+ key,
855
+ value: [{
856
+ operator: '>',
857
+ value: 0
858
+ }]
859
+ })
860
+ }
699
861
 
700
862
  this.extBar.open = true
701
863
  this.extBar.filters = filters
@@ -727,8 +889,8 @@ export default{
727
889
  default:
728
890
  if(e.code.startsWith('Digit')){
729
891
  const idx = parseInt(e.code.substring(5)) - 1
730
- if(this.config.presets[idx]) {
731
- this.selectPreset(idx)
892
+ if(this.presets[idx]) {
893
+ this.selectPreset(this.presets[idx].uid)
732
894
  }
733
895
  }
734
896
  break
@@ -739,16 +901,17 @@ export default{
739
901
 
740
902
  },
741
903
 
742
- components: {PresetBar, PresetSelector, VirtualGrid, VirtualTable},
743
-
744
904
  mounted(){
745
905
  this.$addResizeListener(this.$el, () => this.compPrefix = this.$util.calculateMediaPrefix(this.$el.clientWidth))
746
906
 
747
- this.loadPreset()
907
+ this.loadConfig()
748
908
  .then(() => {
749
909
  this.readyState = 1
750
- this.load()
751
- this.loadExt()
910
+ this.$nextTick(() => {
911
+ this.calcItemsPerPage()
912
+ this.load()
913
+ this.loadExt()
914
+ })
752
915
  })
753
916
 
754
917
  if(this.subscribeKey){
@@ -759,181 +922,94 @@ export default{
759
922
  window.addEventListener('keydown', this.onKeyDown)
760
923
  },
761
924
 
762
- unmounted() {
763
- this.$removeResizeListener(this.$el)
764
- window.removeEventListener('keydown', this.onKeyDown)
765
-
766
- if(this.subscribeKey) {
767
- this.socket.send('user.unsubscribe', {name: this.subscribeKey})
768
- this.socket.off(this.subscribeKey, this.onSignal)
769
- }
770
- },
771
-
772
- computed: {
773
-
774
- columns(){
775
- if((this.preset.pivot ?? {}).enabled){
776
- return this.preset.pivot.columns ?? []
777
-
778
-
779
- const tableColumns = groupBy(this.preset.columns ?? this.config.columns, 'key')
780
-
781
- const columns = []
782
-
783
- for(let row of this.preset.pivot.rows){
784
- if(!tableColumns[row.key][0].defaultFormat)
785
- tableColumns[row.key][0].defaultFormat = tableColumns[row.key][0].format
786
-
787
- switch(row.aggregrate){
788
- case 'year':
789
- tableColumns[row.key][0].format = 'YYYY'
790
- break
791
-
792
- case 'month':
793
- tableColumns[row.key][0].format = 'MMM YYYY'
794
- break
795
-
796
- case 'date':
797
- tableColumns[row.key][0].format = 'D MMM YYYY'
798
- break
799
- }
800
-
801
- columns.push(tableColumns[row.key][0])
802
- }
803
-
804
- for(let value of this.preset.pivot.values){
805
- const key = value.aggregrate ? `_${value.key}-${value.aggregrate}` : `_${value.key}-`
806
- for(let k in tableColumns){
807
- if(k.startsWith(key)){
808
- columns.push(tableColumns[k][0])
809
- }
810
- }
811
- }
812
-
813
- return columns
814
- }
815
-
816
- for(let idx in this.preset.columns){
817
- if(this.preset.columns[idx].defaultFormat){
818
- this.preset.columns[idx].format = this.preset.columns[idx].defaultFormat
819
- delete this.preset.columns[idx].defaultFormat
820
- }
821
- }
822
-
823
- return this.preset.columns
824
- },
825
-
826
- contentSlots(){
827
- const slots = {}
828
- for(let key in this.$slots){
829
- if(!key.startsWith('col-'))
830
- slots[key] = this.$slots[key]
831
- }
832
- return slots
833
- },
834
-
835
- headerSlots(){
836
- const slots = {}
837
- for(let key in this.$slots){
838
- if(key.startsWith('col-'))
839
- slots[key] = this.$slots[key]
840
- }
841
- return slots
842
- },
843
-
844
- computedGridColumn(){
845
- return this.$device.type === 'mobile' ? 1 :
846
- (this.gridColumn ?? 3)
847
- },
848
-
849
- computedPresetMode(){
850
- return this.$device.type === 'mobile' ? 'popup' : this.presetMode
925
+ props: {
926
+ presetKey: String,
927
+ presetSrc: {
928
+ type: String,
929
+ default: 'user.preset'
851
930
  },
852
931
 
853
- dataItems(){
854
- if((this.preset.sorts ?? []).length > 0){
855
- return (this.data.items ?? []).sort((a, b) => {
856
- return sortsFn(a, b, this.preset.sorts, 0)
857
- })
858
- }
859
- else{
860
- return this.data.items ?? []
932
+ subscribeKey: String,
933
+ src: undefined,
934
+ title: String,
935
+ view: {
936
+ type: String,
937
+ validator(value) {
938
+ return ['grid', 'table'].includes(value)
861
939
  }
862
940
  },
863
-
864
- preset(){
865
- return ((this.config ?? {}).presets ?? [])[(this.config ?? {}).presetIdx ?? 0] ?? {}
866
- },
867
-
868
- presetView(){
869
- return this.$device.type === 'mobile' ? 'grid' :
870
- (this.view ?? this.preset.view)
941
+ gridColumn: [ Number, String ],
942
+ gridClass: {
943
+ type: String,
944
+ default: "flex-1 md:p-3"
871
945
  },
872
-
873
- sidebar(){
874
- if(!this.config.sidebar || !('open' in this.config.sidebar))
875
- this.config.sidebar = {
876
- open: false,
877
- width: 270
878
- }
879
-
880
- return this.config.sidebar
946
+ gridContainerClass: {
947
+ type: String,
948
+ default: "md:gap-5"
881
949
  },
882
-
883
- sidebarStyle(){
884
- return {
885
- width: !this.sidebar.open ? 0 : this.sidebar.width + 'px'
886
- }
950
+ headerClass: String,
951
+ presetMode: {
952
+ type: String,
953
+ default: "sidebar"
887
954
  },
888
-
889
- extBar(){
890
- if(!this.config.extbar)
891
- this.config.extbar = {
892
- open: false,
893
- height: Math.round(window.innerHeight / 2.2)
894
- }
895
-
896
- return this.config.extbar
955
+ containerClass: {
956
+ type: String,
957
+ default: "md:p-5 md:gap-3"
897
958
  },
898
-
899
- extStyle(){
900
- return {
901
- height: this.extBar.height + "px"
902
- }
959
+ toolbar: {
960
+ type: [ String, Boolean ],
961
+ default: true
903
962
  },
904
963
 
905
- pivotEnabled(){
906
- return ((this.preset ?? {}).pivot ?? {}).enabled
907
- }
964
+ config: Object,
965
+ defaultConfig: Function,
908
966
 
967
+ sharingSrc: String,
968
+ removeSharingSrc: String,
909
969
  },
910
970
 
911
- data(){
971
+ provide(){
912
972
  return {
913
- readyState: 0,
914
- data: {
915
- itemsPerPage: 16,
916
- },
917
- observer: null,
918
- compPrefix: '',
919
- enumCache: {},
920
- extItems: null,
921
- lastEnumItems: null,
973
+ listStyle: this.$style,
974
+ emitRoot: () => {}
922
975
  }
923
976
  },
924
977
 
925
- provide(){
926
- return {
927
- listStyle: this.$style
978
+ unmounted() {
979
+ this.$removeResizeListener(this.$el)
980
+ window.removeEventListener('keydown', this.onKeyDown)
981
+
982
+ if(this.subscribeKey) {
983
+ this.socket.send('user.unsubscribe', {name: this.subscribeKey})
984
+ this.socket.off(this.subscribeKey, this.onSignal)
928
985
  }
929
986
  },
930
987
 
931
988
  watch: {
932
989
 
933
- config: {
990
+ config(to){
991
+ this.$nextTick(() => {
992
+ this.calcItemsPerPage()
993
+ this.load()
994
+ this.loadExt()
995
+ })
996
+ },
997
+
998
+ cConfig: {
999
+ deep: true,
1000
+ handler(to, from){
1001
+ if(Object.keys(from ?? {}).length > 0){
1002
+ this.saveConfig()
1003
+ }
1004
+ }
1005
+ },
1006
+
1007
+ preset: {
934
1008
  deep: true,
935
- handler(){
936
- this.savePreset()
1009
+ handler(to, from){
1010
+ if(to && to.uid === from?.uid){
1011
+
1012
+ }
937
1013
  }
938
1014
  },
939
1015