@mixd-id/web-scaffold 0.2.240706 → 0.2.250801010

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 (220) hide show
  1. package/docs/components/Dashboard.md +56 -0
  2. package/log.txt +7 -0
  3. package/package.json +27 -19
  4. package/src/components/404.vue +61 -0
  5. package/src/components/AccountIcon.vue +19 -0
  6. package/src/components/Ahref.vue +1 -1
  7. package/src/components/Alert.vue +4 -13
  8. package/src/components/ArrayList.vue +49 -0
  9. package/src/components/Article.vue +24 -30
  10. package/src/components/Button.vue +83 -169
  11. package/src/components/Card.vue +257 -0
  12. package/src/components/Carousel.vue +61 -60
  13. package/src/components/Cart.vue +192 -0
  14. package/src/components/CartIcon.vue +89 -0
  15. package/src/components/ChartBar.vue +2 -3
  16. package/src/components/Checkbox.vue +20 -11
  17. package/src/components/Checkout.vue +373 -0
  18. package/src/components/CheckoutDelivery.vue +267 -0
  19. package/src/components/CodeEditor.vue +5 -16
  20. package/src/components/CollapsiblePanel.vue +70 -0
  21. package/src/components/ColorPicker.vue +12 -5
  22. package/src/components/ColorPicker2.vue +41 -19
  23. package/src/components/ColorPicker3.vue +100 -0
  24. package/src/components/Confirm.vue +9 -7
  25. package/src/components/ContextMenu.vue +122 -206
  26. package/src/components/ContextMenuItem.vue +53 -0
  27. package/src/components/Dashboard.vue +243 -0
  28. package/src/components/Dashboard2.vue +118 -0
  29. package/src/components/DashboardComponentSelector.vue +96 -0
  30. package/src/components/DashboardConfigs.vue +202 -0
  31. package/src/components/Datepicker.vue +102 -41
  32. package/src/components/DayTimeRange.vue +3 -2
  33. package/src/components/Dropdown.vue +7 -4
  34. package/src/components/Flex.vue +14 -40
  35. package/src/components/GHeatMaps.vue +2 -2
  36. package/src/components/Grid.vue +6 -6
  37. package/src/components/HTMLEditor.vue +27 -14
  38. package/src/components/Image.vue +62 -108
  39. package/src/components/ImagePreview.vue +14 -4
  40. package/src/components/ImageUploader.vue +114 -0
  41. package/src/components/ImportModal.vue +3 -3
  42. package/src/components/Link.vue +62 -6
  43. package/src/components/List.vue +528 -403
  44. package/src/components/ListContextMenu.vue +88 -0
  45. package/src/components/ListItem.vue +6 -4
  46. package/src/components/ListPage1.vue +14 -15
  47. package/src/components/ListView.vue +5 -6
  48. package/src/components/ListViewSettings.vue +2 -2
  49. package/src/components/LogViewerItem.vue +1 -1
  50. package/src/components/MarkdownEdit.vue +128 -0
  51. package/src/components/MarkdownPreview.vue +102 -0
  52. package/src/components/MenuItem1.vue +36 -0
  53. package/src/components/Modal.vue +95 -43
  54. package/src/components/MultiDropdown.vue +124 -0
  55. package/src/components/MultilineText.vue +1 -4
  56. package/src/components/OTPField.vue +40 -26
  57. package/src/components/ObjectTree.vue +1 -1
  58. package/src/components/PageBuilder.vue +3 -3
  59. package/src/components/Paragraph.vue +1 -2
  60. package/src/components/PresetSelectorFilterItem.vue +107 -95
  61. package/src/components/Radio.vue +1 -1
  62. package/src/components/SearchModal.vue +153 -0
  63. package/src/components/Slider.vue +1 -1
  64. package/src/components/Svg.vue +1 -1
  65. package/src/components/SvgEditor.vue +173 -0
  66. package/src/components/Switch.vue +4 -5
  67. package/src/components/Table.vue +2 -2
  68. package/src/components/TableView.vue +2 -3
  69. package/src/components/TableViewHead.vue +2 -2
  70. package/src/components/Tabs.vue +1 -1
  71. package/src/components/Testimonial.vue +2 -2
  72. package/src/components/Text.vue +7 -22
  73. package/src/components/TextEditor.vue +3 -3
  74. package/src/components/TextWithTag.vue +61 -30
  75. package/src/components/Textarea.vue +16 -22
  76. package/src/components/Textbox.vue +9 -19
  77. package/src/components/Timepicker.vue +25 -15
  78. package/src/components/Toast.vue +5 -3
  79. package/src/components/TreeMenu.vue +122 -0
  80. package/src/components/TreeView.vue +15 -10
  81. package/src/components/TreeView2.vue +38 -0
  82. package/src/components/TreeViewItem.vue +58 -29
  83. package/src/components/TreeViewItem2.vue +55 -0
  84. package/src/components/Uploader.vue +45 -0
  85. package/src/components/Video.vue +119 -0
  86. package/src/components/VirtualGrid.vue +24 -7
  87. package/src/components/VirtualTable.vue +363 -128
  88. package/src/configs/dashboard/data-table.js +9 -0
  89. package/src/configs/web-page-builder.js +118 -0
  90. package/src/directives/intersect.js +26 -0
  91. package/src/hooks/device.js +14 -0
  92. package/src/index.js +62 -107
  93. package/src/mixin/component.js +151 -67
  94. package/src/themes/default/index.js +118 -159
  95. package/src/utils/dashboard.js +22 -962
  96. package/src/utils/helpers.cjs +635 -0
  97. package/src/utils/helpers.js +91 -60
  98. package/src/utils/helpers.mjs +245 -12
  99. package/src/utils/importer.js +22 -3
  100. package/src/utils/list.mjs +1509 -0
  101. package/src/utils/preset-selector.cjs +1455 -0
  102. package/src/utils/preset-selector.js +489 -95
  103. package/src/utils/preset-selector.mjs +59 -20
  104. package/src/utils/queue.js +63 -0
  105. package/src/utils/web.mjs +120 -0
  106. package/src/utils/wss.js +38 -36
  107. package/src/utils/wss.mjs +24 -19
  108. package/src/widgets/AhrefSetting.vue +16 -13
  109. package/src/widgets/ArticleSetting.vue +15 -27
  110. package/src/widgets/BackgroundColorSetting.vue +153 -0
  111. package/src/widgets/BorderColorSetting.vue +57 -0
  112. package/src/widgets/BotEditor/BotEditorActions.vue +3 -2
  113. package/src/widgets/BotEditor/BotEditorSettings.vue +21 -0
  114. package/src/widgets/BotEditor.vue +35 -15
  115. package/src/widgets/ButtonSetting.vue +12 -13
  116. package/src/widgets/CarouselSetting.vue +33 -45
  117. package/src/widgets/CartSetting.vue +46 -0
  118. package/src/widgets/CheckoutSetting.vue +46 -0
  119. package/src/widgets/CollapsiblePanelSetting.vue +46 -0
  120. package/src/widgets/ColumnSelector.vue +29 -5
  121. package/src/widgets/ComponentSetting.vue +1 -1
  122. package/src/widgets/ComponentSetting2.vue +145 -236
  123. package/src/widgets/ComponentSetting3.vue +1 -1
  124. package/src/widgets/ContactForm.vue +3 -3
  125. package/src/widgets/ContactFormSetting.vue +41 -30
  126. package/src/widgets/Dashboard/BarChart.vue +47 -11
  127. package/src/widgets/Dashboard/BarChartSetting.vue +1 -1
  128. package/src/widgets/Dashboard/DataTable.vue +125 -0
  129. package/src/widgets/Dashboard/DataTableSetting.vue +243 -0
  130. package/src/widgets/Dashboard/DatasourceSelector.vue +1 -1
  131. package/src/widgets/Dashboard/Doughnut.vue +49 -7
  132. package/src/widgets/Dashboard/DoughnutSetting.vue +2 -2
  133. package/src/widgets/Dashboard/Metric.vue +78 -19
  134. package/src/widgets/Dashboard/MetricSetting.vue +81 -28
  135. package/src/widgets/Dashboard/Pie.vue +55 -6
  136. package/src/widgets/Dashboard/PieSetting.vue +1 -1
  137. package/src/widgets/Dashboard/PolarArea.vue +49 -7
  138. package/src/widgets/Dashboard/PolarAreaSetting.vue +1 -1
  139. package/src/widgets/Dashboard/SharingModal.vue +4 -5
  140. package/src/widgets/Dashboard/ViewSelector.vue +2 -2
  141. package/src/widgets/Dashboard/VirtualTableSetting.vue +121 -184
  142. package/src/widgets/{Dashboard.vue → Dashboard0.vue} +426 -343
  143. package/src/widgets/EmbeddedVideoSetting.vue +7 -5
  144. package/src/widgets/FAQ.vue +16 -3
  145. package/src/widgets/FAQSetting.vue +53 -47
  146. package/src/widgets/FeatureList.vue +3 -0
  147. package/src/widgets/FeatureListSetting.vue +112 -102
  148. package/src/widgets/FlexSetting.vue +83 -106
  149. package/src/widgets/GridSetting.vue +71 -196
  150. package/src/widgets/Header2.vue +34 -71
  151. package/src/widgets/Header2Setting.vue +95 -179
  152. package/src/widgets/HeaderSetting.vue +16 -18
  153. package/src/widgets/IconListSetting.vue +69 -65
  154. package/src/widgets/ImageSetting.vue +33 -60
  155. package/src/widgets/LinkSetting.vue +60 -37
  156. package/src/widgets/LinkSettingModal.vue +173 -0
  157. package/src/widgets/LogViewer.vue +1 -1
  158. package/src/widgets/MarginSetting.vue +2 -2
  159. package/src/widgets/MenuEditor.vue +1 -1
  160. package/src/widgets/MenuItem1Setting.vue +78 -0
  161. package/src/widgets/ModalSetting.vue +42 -44
  162. package/src/widgets/MultiValueSetting.vue +2 -2
  163. package/src/widgets/MultiValueSetting2.vue +78 -45
  164. package/src/widgets/OGSettingModal.vue +103 -0
  165. package/src/widgets/PaddingSetting.vue +2 -2
  166. package/src/widgets/ParagraphSetting.vue +16 -13
  167. package/src/widgets/PositionSetting.vue +209 -0
  168. package/src/widgets/PresetBar.vue +359 -210
  169. package/src/widgets/PresetBarPivot.vue +31 -19
  170. package/src/widgets/PresetSelector.vue +29 -17
  171. package/src/widgets/SearchModalSetting.vue +70 -0
  172. package/src/widgets/Share.vue +1 -2
  173. package/src/widgets/ShareSetting.vue +67 -60
  174. package/src/widgets/StyleSetting.vue +365 -150
  175. package/src/widgets/TestimonialSetting.vue +97 -88
  176. package/src/widgets/TextBlockSetting.vue +16 -13
  177. package/src/widgets/UserActionBuilder/UserActionConsole.vue +30 -10
  178. package/src/widgets/UserActionBuilder/UserActionOutput.vue +2 -2
  179. package/src/widgets/UserActionBuilder/UserActionOutputReply.vue +64 -87
  180. package/src/widgets/UserActionBuilder/UserActionProps.vue +3 -3
  181. package/src/widgets/UserActionBuilder.vue +4 -16
  182. package/src/widgets/WebComponentSelector.vue +15 -11
  183. package/src/widgets/WebLayoutSelector.vue +41 -270
  184. package/src/widgets/WebPageBuilder.vue +1019 -707
  185. package/src/widgets/WebPageBuilder2.vue +7 -7
  186. package/src/widgets/WebPageBuilder4/ButtonSetting.vue +0 -8
  187. package/src/widgets/WebPageBuilder4/CarouselSetting.vue +63 -7
  188. package/src/widgets/WebPageBuilder4/FlexAlignSetting.vue +3 -3
  189. package/src/widgets/WebPageBuilder4/FlexSetting.vue +1 -10
  190. package/src/widgets/WebPageBuilder4/MultiValueSetting.vue +2 -2
  191. package/src/widgets/WebPageBuilder4/PropertySetting.vue +0 -7
  192. package/src/widgets/WebPageBuilder4/WebPageComponentSelector.vue +1 -7
  193. package/src/widgets/WebPageBuilder4.vue +289 -575
  194. package/src/widgets/WebPageSelector.vue +1 -1
  195. package/src/widgets/YoutubeVideoSetting.vue +16 -13
  196. package/tailwind.config.js +3 -35
  197. package/docs/schema/user-action.json +0 -266
  198. package/src/App.vue +0 -25
  199. package/src/components/SearchButton.vue +0 -57
  200. package/src/entry-client.js +0 -27
  201. package/src/entry-server.js +0 -73
  202. package/src/events/event.js +0 -2
  203. package/src/main.js +0 -29
  204. package/src/mixin/website.js +0 -121
  205. package/src/router.js +0 -57
  206. package/src/widgets/MobileMenu.vue +0 -182
  207. package/src/widgets/WebPageBuilder4/ActionSetting.vue +0 -158
  208. package/src/widgets/WebPageBuilder4/ColorSetting.vue +0 -63
  209. package/src/widgets/WebPageBuilder4/DataSetting.vue +0 -92
  210. package/src/widgets/WebPageBuilder4/FontSizeSetting.vue +0 -76
  211. package/src/widgets/WebPageBuilder4/LinkSetting.vue +0 -68
  212. package/src/widgets/WebPageBuilder4/MobileMenuSetting.vue +0 -106
  213. package/src/widgets/WebPageBuilder4/Setting.vue +0 -73
  214. package/src/widgets/WebPageBuilder4/StyleSetting.vue +0 -77
  215. package/src/widgets/WebPageBuilder4/SvgSetting.vue +0 -207
  216. package/src/widgets/WebPageBuilder4/TextTransformSetting.vue +0 -70
  217. package/src/widgets/WebPageBuilder4/WebPageDataEdit.vue +0 -121
  218. package/test.json +0 -22
  219. /package/src/widgets/{Header1.vue → Header0.vue} +0 -0
  220. /package/src/widgets/{Header1Setting.vue → Header0Setting.vue} +0 -0
@@ -1,24 +1,20 @@
1
1
  <template>
2
- <div class="flex flex-col">
2
+ <div class="flex flex-col bg-base-400 md:bg-transparent"
3
+ v-intersect="onVisibleChange">
3
4
 
4
5
  <div v-if="readyState >= 1"
5
6
  class="flex-1 flex flex-row">
6
7
 
7
8
  <div v-if="computedPresetMode === 'sidebar' && sidebar.open"
8
- class="relative flex flex-col border-r-[1px] border-text-50 panel-400"
9
+ class="relative flex flex-col border-r-[1px] border-border-50"
9
10
  :style="sidebarStyle">
10
11
 
11
12
  <PresetBar :config="cConfig"
12
- :presets="presets"
13
- :shared-presets="sharedPresets"
14
13
  class="flex-1"
15
- :can-share="!!sharingSrc"
16
- :sharing-src="sharingSrc"
17
- :remove-sharing-src="removeSharingSrc"
18
14
  :enumCache="enumCache"
19
- @duplicate="duplicate"
20
- @remove="removePreset"
21
- @remove-shared="removeShared"
15
+ :controller="controller"
16
+ :preset-key="presetKey"
17
+ :use-assistant="useAssistant"
22
18
  @apply="load">
23
19
 
24
20
  <template #toolbar>
@@ -34,40 +30,40 @@
34
30
  @mousedown="(e) => $util.dragResize(e, resize1)"></div>
35
31
  </div>
36
32
 
37
- <div class="flex-1 flex flex-col" v-if="preset">
33
+ <div v-if="preset" class="flex-1 flex flex-col relative">
38
34
 
39
35
  <div class="flex-1 flex flex-col" :class="containerClass">
40
36
  <slot name="head"
41
37
  :preset="preset"
42
38
  :presetSelector="$refs.presetSelector"
43
39
  :compPrefix="compPrefix">
44
- <div class="flex flex-col md:flex-row gap-3 md:items-start overflow-hidden leading-tight p-3 md:p-0 md:pl-3 panel-400 md:panel-none border-b-[1px] border-text-50 md:border-none"
40
+ <div class="flex flex-col md:flex-row gap-3 md:items-end overflow-hidden p-3 pb-1 md:p-0"
45
41
  :class="headerClass">
42
+ <div class="flex md:flex-1 flex-row items-end md:gap-3 py-1 md:py-0">
43
+ <slot name="left"></slot>
46
44
 
47
- <div class="flex md:flex-1 flex-row items-start gap-3">
48
-
49
- <div class="flex flex-row gap-2 md:gap-3 items-center" ref="title">
45
+ <div class="flex flex-row gap-2 md:gap-3 items-center px-3" ref="title">
50
46
  <div class="flex flex-col whitespace-nowrap text-ellipsis overflow-hidden">
51
- <div class="cursor-pointer group" @click="$refs.contextMenu.open($refs.title)">
47
+ <div class="cursor-pointer group leading-4" @click="$refs.contextMenu.open($refs.title)">
52
48
  <small class="text-text-400 text-xs">{{ title ?? 'Untitled' }}</small>
53
49
  <div class="flex flex-row items-baseline gap-1">
54
- <h5 class="whitespace-nowrap relative top-[-2px] text-ellipsis overflow-hidden group-hover:text-primary">
55
- {{ preset.params.name ?? 'Preset Name' }}
56
- </h5>
50
+ <h3 class="whitespace-nowrap relative text-ellipsis overflow-hidden group-hover:text-primary">
51
+ {{ preset.name ?? 'Preset Name' }}
52
+ </h3>
57
53
  <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>
58
54
  </div>
59
55
  </div>
60
- <ContextMenu ref="contextMenu" class="panel-400">
61
- <div class="flex flex-col min-w-[300px] divide-y divide-text-50">
56
+ <ContextMenu ref="contextMenu" class="bg-base-300 mt-4">
57
+ <div class="flex flex-col min-w-[280px] max-w-[400px] p-1">
62
58
 
63
- <div class="p-3 flex flex-col gap-1 bg-base-300">
64
- <button v-for="(preset, idx) in presets"
59
+ <div class="flex flex-col gap-1 bg-base-300">
60
+ <button v-for="(preset, idx) in configPresets"
65
61
  class="p-2 px-5 text-left flex flex-row items-center rounded-md"
66
- :class="cConfig.params.presetIdx === preset.uid ? 'bg-primary-200 text-white' : 'hover:bg-primary-100'"
62
+ :class="configParams.presetIdx === preset.uid ? 'bg-primary-200 text-white' : 'hover:bg-primary-100'"
67
63
  type="button"
68
- @click="selectPreset(preset.uid)">
69
- <label class="flex-1 pr-12">
70
- {{ preset.params.name }}
64
+ @click="selectPreset(idx)">
65
+ <label class="flex-1 pr-12 text-ellipsis-nowrap">
66
+ {{ preset.name }}
71
67
  </label>
72
68
  <div class="p-1">
73
69
  <div v-if="idx < 10"
@@ -78,8 +74,10 @@
78
74
  </button>
79
75
  </div>
80
76
 
77
+ <div class="my-2 h-[1px] bg-border-50"></div>
78
+
81
79
  <div class="px-3">
82
- <button type="button" class="text-primary p-5 text-left w-full" @click="openPreset">Edit</button>
80
+ <button type="button" class="text-primary p-3 text-left w-full" @click="openPreset">Edit</button>
83
81
  </div>
84
82
 
85
83
  </div>
@@ -87,61 +85,76 @@
87
85
  </div>
88
86
  </div>
89
87
 
88
+ <slot name="left-toolbar"></slot>
89
+
90
90
  <div class="flex-1"></div>
91
91
 
92
- <slot name="toolbar"></slot>
92
+ <slot name="toolbar">
93
+ </slot>
94
+
95
+ <slot name="right-toolbar">
96
+ <div class="hidden md:flex flex-row gap-2" v-if="Boolean(toolbar)">
97
+ <Textbox v-if="canSearch"
98
+ class="flex-1 md:w-[240px]"
99
+ placeholder="Search..."
100
+ clearable="1"
101
+ maxlength="100"
102
+ v-model="preset.search"
103
+ @clear="delete preset.search; load()"
104
+ @keyup.enter="load">
105
+ <template #start>
106
+ <div class="p-2 pr-0">
107
+ <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M504.1 471l-134-134C399.1 301.5 415.1 256.8 415.1 208c0-114.9-93.13-208-208-208S-.0002 93.13-.0002 208S93.12 416 207.1 416c48.79 0 93.55-16.91 129-45.04l134 134C475.7 509.7 481.9 512 488 512s12.28-2.344 16.97-7.031C514.3 495.6 514.3 480.4 504.1 471zM48 208c0-88.22 71.78-160 160-160s160 71.78 160 160s-71.78 160-160 160S48 296.2 48 208z"/></svg>
108
+ </div>
109
+ </template>
110
+ </Textbox>
111
+ </div>
112
+ </slot>
93
113
  </div>
94
114
 
95
- <div class="flex flex-row gap-2 gap-1" v-if="Boolean(toolbar)">
96
- <Textbox class="flex-1 md:w-[240px]" placeholder="Search..." clearable="1"
115
+ </div>
116
+ </slot>
117
+
118
+ <div class="md:hidden">
119
+ <slot name="mobile-toolbar">
120
+ <div class="p-3">
121
+ <Textbox v-if="canSearch"
122
+ placeholder="Search..."
123
+ class="bg-base-300"
124
+ clearable="1"
125
+ maxlength="100"
97
126
  v-model="preset.search"
98
127
  @clear="delete preset.search; load()"
99
128
  @keyup.enter="load">
100
129
  <template #start>
101
130
  <div class="p-2 pr-0">
102
- <svg width="16" height="16" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M504.1 471l-134-134C399.1 301.5 415.1 256.8 415.1 208c0-114.9-93.13-208-208-208S-.0002 93.13-.0002 208S93.12 416 207.1 416c48.79 0 93.55-16.91 129-45.04l134 134C475.7 509.7 481.9 512 488 512s12.28-2.344 16.97-7.031C514.3 495.6 514.3 480.4 504.1 471zM48 208c0-88.22 71.78-160 160-160s160 71.78 160 160s-71.78 160-160 160S48 296.2 48 208z"/></svg>
131
+ <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M504.1 471l-134-134C399.1 301.5 415.1 256.8 415.1 208c0-114.9-93.13-208-208-208S-.0002 93.13-.0002 208S93.12 416 207.1 416c48.79 0 93.55-16.91 129-45.04l134 134C475.7 509.7 481.9 512 488 512s12.28-2.344 16.97-7.031C514.3 495.6 514.3 480.4 504.1 471zM48 208c0-88.22 71.78-160 160-160s160 71.78 160 160s-71.78 160-160 160S48 296.2 48 208z"/></svg>
103
132
  </div>
104
133
  </template>
105
134
  </Textbox>
106
-
107
- <div v-if="$slots.gridItem"
108
- class="hidden md:grid grid-cols-2 gap-[1px] border-[1px] border-text-50 rounded-lg">
109
- <Radio v-model="preset.view"
110
- name="view"
111
- value="table"
112
- :custom="true"
113
- class="p-2 px-3 rounded-lg rounded-r-none"
114
- :class="preset.view === 'table' ? 'bg-primary-500' : 'bg-base-500'">
115
- <svg width="14" height="14" :class="preset.view === 'table' ? 'fill-white' : 'fill-text'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M448 32H64.05C28.7 32 .0492 60.65 .0492 96v320c0 35.35 28.65 64 64 64h383.1c35.35 0 64-28.65 64-64V96C512 60.65 483.4 32 448 32zM224 416H64v-96h160V416zM224 256H64V160h160V256zM448 416h-160v-96h160V416zM448 256h-160V160h160V256z"/></svg>
116
- </Radio>
117
- <Radio v-model="preset.view"
118
- name="view"
119
- value="grid"
120
- :custom="true"
121
- class="p-2 px-3 bg-base-500 rounded-lg rounded-l-none"
122
- :class="preset.view === 'grid' ? 'bg-primary-500' : 'bg-base-500'">
123
- <svg width="14" height="14" :class="preset.view === 'grid' ? 'fill-white' : 'fill-text'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M144 288h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32v-96C176 302.3 161.7 288 144 288zM368 288h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32v-96C400 302.3 385.7 288 368 288zM592 288h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32v-96C624 302.3 609.7 288 592 288zM144 64h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32V96C176 78.33 161.7 64 144 64zM368 64h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32V96C400 78.33 385.7 64 368 64zM592 64h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32V96C624 78.33 609.7 64 592 64z"/></svg>
124
- </Radio>
125
- </div>
126
135
  </div>
127
-
128
- </div>
129
- </slot>
136
+ </slot>
137
+ </div>
130
138
 
131
139
  <div class="flex-1 flex" ref="content">
132
140
  <div v-if="readyState === 2" class="flex-1 flex items-center justify-center">
133
141
  <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
142
  </div>
135
143
 
136
- <div v-else-if="presetView === 'table' || pivotEnabled" class="flex-1 flex" :class="pivotEnabled ? 'p-3 panel-400 md:p-0' : ''">
144
+ <div v-else-if="presetView === 'table' || pivotEnabled" class="flex-1 flex flex-col" :class="pivotEnabled ? 'p-3 bg-base-400 md:p-0' : ''">
137
145
  <VirtualTable
138
146
  ref="table"
139
147
  :columns="columns"
140
- class="flex-1 rounded-lg panel-400"
141
- :items="data.items"
148
+ class="flex-1"
149
+ :items="dataItems"
142
150
  :enumCache="enumCache"
151
+ :freeze-left="freezeLeft"
152
+ :item-class="itemClass"
153
+ @freeze="freeze"
154
+ @unfreeze="unfreeze"
143
155
  @scroll-end="loadNext"
144
- @item-click="onTableItemClick">
156
+ @item-click="onTableItemClick"
157
+ @dragover="(...args) => $emit('dragover', ...args)">
145
158
 
146
159
  <template v-for="(_, slot) in headerSlots" #[slot]="{ item, index }">
147
160
  <div :class="getHeader(slot.replace('col-', ''))">
@@ -149,13 +162,13 @@
149
162
  </div>
150
163
  </template>
151
164
 
152
- <template v-for="(_, slot) in contentSlots" #[slot]="{ item, index }">
153
- <slot :name="slot" :item="item" :index="index"></slot>
165
+ <template v-for="(_, slot) in contentSlots" #[slot]="{ column, item, index, selected }">
166
+ <slot :name="slot" :item="item" :index="index" :selected="selected" :column="column"></slot>
154
167
  </template>
155
168
 
156
169
  <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>
170
+ <div v-if="data?.hasNext" class="flex justify-center pt-3 pb-5">
171
+ <svg v-if="readyState === 4" class="animate-spin w-[21px] h-[21px]" 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
172
  <button v-else type="button" class="text-text-300" @click="loadNext()">
160
173
  {{ $t('Load More' )}}
161
174
  </button>
@@ -172,10 +185,10 @@
172
185
  :class="gridClass"
173
186
  @scroll-end="loadNext"
174
187
  :container-class="`${gridContainerClass}`"
175
- :config="config">
188
+ :config="cConfig">
176
189
  <template #item="{ item }">
177
190
  <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">
191
+ <div class="flex flex-row bg-base-400 rounded-lg md:rounded-lg overflow-hidden p-3 gap-3">
179
192
  <div>
180
193
  <Image :src="item.imageUrl" class="bg-text-50 w-[64px] h-[64px] rounded-lg" />
181
194
  </div>
@@ -197,12 +210,12 @@
197
210
  </template>
198
211
  </VirtualGrid>
199
212
 
200
- <PresetSelector ref="presetSelector" :config="config" @select="load" />
213
+ <PresetSelector ref="presetSelector" :config="cConfig" @select="load" />
201
214
  </div>
202
215
  </div>
203
216
 
204
217
  <div v-if="extBar.open && pivotEnabled"
205
- :style="extStyle" class="border-t-[1px] border-text-50 flex flex-col relative md:p-5">
218
+ :style="extStyle" class="border-t-[1px] border-border-50 flex flex-col relative md:p-5">
206
219
 
207
220
  <div v-if="readyState === 3" class="flex-1 flex items-center justify-center">
208
221
  <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>
@@ -210,8 +223,8 @@
210
223
 
211
224
  <VirtualTable v-else-if="presetView === 'table'"
212
225
  ref="table"
213
- :columns="preset.params.columns"
214
- class="flex-1 rounded-lg panel-400"
226
+ :columns="preset.columns"
227
+ class="flex-1 bg-base-400"
215
228
  :items="data.extItems"
216
229
  :enumCache="enumCache"
217
230
  @scroll-end="loadExtNext">
@@ -235,10 +248,10 @@
235
248
  :class="gridClass"
236
249
  @scroll-end="loadExtNext"
237
250
  :container-class="`${gridContainerClass}`"
238
- :config="config">
251
+ :config="cConfig">
239
252
  <template #item="{ item }">
240
253
  <slot name="gridItem" :item="item" :getEnumText="getEnumText">
241
- <div class="flex flex-row panel-400 rounded-lg overflow-hidden md:rounded-lg overflow-hidden p-3 gap-3">
254
+ <div class="flex flex-row bg-base-400 rounded-lg md:rounded-lg overflow-hidden p-3 gap-3">
242
255
  <div>
243
256
  <Image :src="item.imageUrl" class="bg-text-50 w-[64px] h-[64px] rounded-lg" />
244
257
  </div>
@@ -278,145 +291,69 @@ import VirtualGrid from "./VirtualGrid.vue";
278
291
  import PresetSelector from "../widgets/PresetSelector.vue";
279
292
  import {generatePivotColumns, generateTotalColumns, setupConfig, sortsFn} from "../utils/preset-selector.mjs";
280
293
  import PresetBar from "../widgets/PresetBar.vue";
281
- import {invokeAfterIdle} from "../utils/helpers.mjs";
294
+ import {invokeAfterIdle, queueForLater} from "../utils/helpers.mjs";
282
295
 
283
296
  export default{
284
297
 
285
- components: {PresetBar, PresetSelector, VirtualGrid, VirtualTable},
298
+ emits: [ 'dragover', 'after-load', 'open-preset', 'signal', 'pivot-item-click' ],
286
299
 
287
- computed: {
288
-
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
302
- },
300
+ inject: [ 'useSocket', 'toast' ],
303
301
 
304
- contentSlots(){
305
- const slots = {}
306
- for(let key in this.$slots){
307
- if(!key.startsWith('col-'))
308
- slots[key] = this.$slots[key]
309
- }
310
- return slots
311
- },
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
320
- },
321
-
322
- computedGridColumn(){
323
- return this.$device.type === 'mobile' ? 1 :
324
- (this.gridColumn ?? 3)
325
- },
326
-
327
- computedPresetMode(){
328
- return this.$device.type === 'mobile' ? 'popup' : this.presetMode
302
+ props: {
303
+ config: Object,
304
+ defaultConfig: Function,
305
+ presetKey: String,
306
+ subscribeKey: String,
307
+ controller: String,
308
+ title: String,
309
+ updateInterval: {
310
+ type: Number,
311
+ default: 1200
329
312
  },
330
313
 
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 ?? []
314
+ src: undefined,
315
+ view: {
316
+ type: String,
317
+ validator(value) {
318
+ return ['grid', 'table'].includes(value)
339
319
  }
340
320
  },
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)
321
+ gridColumn: [ Number, String ],
322
+ gridClass: {
323
+ type: String,
324
+ default: "flex-1 md:p-3"
356
325
  },
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
326
+ gridContainerClass: {
327
+ type: String,
328
+ default: "md:gap-5"
366
329
  },
367
-
368
- sidebarStyle(){
369
- return {
370
- width: !this.sidebar.open ? 0 : this.sidebar.width + 'px'
371
- }
330
+ headerClass: String,
331
+ presetMode: {
332
+ type: String,
333
+ default: "sidebar"
372
334
  },
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
335
+ containerClass: {
336
+ type: String,
337
+ default: "md:p-8 md:gap-5"
382
338
  },
383
-
384
- extStyle(){
385
- return {
386
- height: this.extBar.height + "px"
387
- }
339
+ itemClass: String,
340
+ toolbar: {
341
+ type: [ String, Boolean ],
342
+ default: true
388
343
  },
389
-
390
- pivotEnabled(){
391
- return ((this.preset ?? {}).pivot ?? {}).enabled
344
+ sorts: [ Array, Function ],
345
+ itemsFn: Function,
346
+ onSignalFn: Function,
347
+ searchable: {
348
+ type: undefined,
349
+ default: true
392
350
  },
393
-
394
- cConfig(){
395
- return this.config ?? this._config
351
+ useAssistant: {
352
+ type: undefined,
353
+ default: false
396
354
  }
397
-
398
355
  },
399
356
 
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: {}
413
- }
414
- },
415
-
416
- emits: [ 'after-load', 'open-preset', 'signal', 'pivot-item-click' ],
417
-
418
- inject: [ 'socket', 'toast' ],
419
-
420
357
  methods: {
421
358
 
422
359
  calcItemsPerPage(){
@@ -427,10 +364,6 @@ export default{
427
364
 
428
365
  },
429
366
 
430
- duplicate(){
431
- this.saveConfig(false, true)
432
- },
433
-
434
367
  getEnumText(key, value){
435
368
 
436
369
  let enumText
@@ -460,25 +393,26 @@ export default{
460
393
  },
461
394
 
462
395
  load(){
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))
396
+ if(this.computedSrc){
397
+ if(!this.preset?.columns){
398
+ this.preset.columns = JSON.parse(JSON.stringify(this.cConfig.columns))
466
399
  }
467
400
 
468
401
  this.readyState = 2
469
- return this.socket.send(this.src, {
470
- ...this.preset.params,
402
+ return this.useSocket().send(this.computedSrc, {
403
+ ...this.preset,
404
+ id: undefined,
405
+ uid: undefined,
471
406
  itemsPerPage: this.data.itemsPerPage,
472
407
  })
473
408
  .then(data => {
474
- generatePivotColumns(this.preset, data.items)
475
- generateTotalColumns(this.preset, data.items)
476
- this.loadEnums(data.items)
477
- Object.assign(this.data, data ?? {})
478
- this.$emit('after-load')
479
- })
480
- .catch(err => {
481
- this.toast(err)
409
+ if(data?.items){
410
+ generatePivotColumns(this.preset, data.items)
411
+ generateTotalColumns(this.preset, data.items)
412
+ Object.assign(this.data, data ?? {})
413
+ this.loadEnums(this.dataItems)
414
+ this.$emit('after-load')
415
+ }
482
416
  })
483
417
  .finally(_ => this.readyState = 1)
484
418
  }
@@ -486,19 +420,27 @@ export default{
486
420
  },
487
421
 
488
422
  loadNext(){
489
- if(this.src &&
423
+ if(this.computedSrc && this.readyState !== 4 &&
490
424
  ((this.data ?? {}).hasNext !== false) &&
491
- !(this.preset.pivot ?? {}).enabled) {
425
+ !(this.preset.pivot ?? {}).enabled &&
426
+ this.dataItems) {
427
+
428
+ const afterItem = this.dataItems[this.dataItems.length - 1]
492
429
 
493
430
  this.readyState = 4
494
- this.socket.send(this.src, {
431
+ this.useSocket().send(this.computedSrc, {
495
432
  ...this.preset,
433
+ id: undefined,
434
+ uid: undefined,
496
435
  itemsPerPage: this.data.itemsPerPage,
497
- afterItem: this.data.items[this.data.items.length - 1],
436
+ afterItem,
498
437
  })
499
438
  .then(data => {
500
- this.data.items.push(...data.items)
501
439
  this.loadEnums(data.items)
440
+ .then(() => {
441
+ this.data.items.push(...data.items)
442
+ this.data.hasNext = data.hasNext
443
+ })
502
444
  })
503
445
  .catch(err => this.toast(err))
504
446
  .finally(_ => this.readyState = 1)
@@ -506,10 +448,60 @@ export default{
506
448
  return new Promise(resolve => resolve())
507
449
  },
508
450
 
509
- loadEnums(items){
451
+ async loadPreset(){
452
+ if(!Object.keys(this.$route.query).map(_ => _.toLowerCase()).includes('reset')){
453
+ if(this.presetKey){
454
+ const config = await this.useSocket().send(this.presetSrc, { key:this.presetKey })
455
+ if(config){
456
+ if((config.presets ?? []).length > 0){
457
+ Object.assign(this.cConfig, config)
458
+ }
459
+ else{
460
+ const defaultConfig = setupConfig(await this.defaultConfig())
461
+ Object.assign(this.cConfig, defaultConfig)
462
+ }
510
463
 
511
- for(let i in this.preset.params.columns){
512
- const column = this.preset.params.columns[i]
464
+ if(this.$route.query?.search)
465
+ this.preset.search = this.$route.query.search
466
+ }
467
+ else{
468
+ const defaultConfig = setupConfig(typeof this.defaultConfig === 'function' ? await this.defaultConfig() : {})
469
+ Object.assign(this.cConfig, defaultConfig)
470
+ }
471
+ }
472
+ else{
473
+ const defaultConfig = setupConfig(typeof this.defaultConfig === 'function' ? await this.defaultConfig() : {})
474
+ Object.assign(this.cConfig, defaultConfig)
475
+
476
+ return new Promise(resolve => resolve())
477
+ }
478
+ }
479
+ else{
480
+ if(this.defaultConfig){
481
+ const defaultConfig = setupConfig(await this.defaultConfig())
482
+ Object.assign(this.cConfig, defaultConfig)
483
+ }
484
+
485
+ return new Promise((resolve) => {
486
+ const query = {}
487
+ for(let key in this.$route.query){
488
+ if(key.toLowerCase() !== 'reset')
489
+ query[key] = this.$route.query[key]
490
+ }
491
+ this.$router.replace({
492
+ ...this.$route,
493
+ query
494
+ })
495
+
496
+ resolve()
497
+ })
498
+ }
499
+ },
500
+
501
+ async loadEnums(items){
502
+ if(!items) return
503
+
504
+ return Promise.all((this.preset.columns ?? []).map(async column => {
513
505
 
514
506
  if(column.enumSrc) {
515
507
 
@@ -527,7 +519,7 @@ export default{
527
519
  if(reqItems.length > 0 && reqItems !== this.lastEnumItems){
528
520
 
529
521
  const [ src, key, attr ] = column.enumSrc.split(':')
530
- this.socket.send(src, {
522
+ const res = await this.useSocket().send(src, {
531
523
  columns: [
532
524
  { key:attr, visible:true }
533
525
  ],
@@ -536,35 +528,35 @@ export default{
536
528
  key:key, value: [ { operator:'in', value:reqItems } ]
537
529
  }
538
530
  ]
531
+ });
532
+
533
+ (res.items ?? []).forEach(_ => {
534
+ this.enumCache[column.key][_[key]] = {
535
+ text: _[attr] ?? _[key],
536
+ value: _[key],
537
+ }
539
538
  })
540
- .then(res => {
541
- (res.items ?? []).forEach(_ => {
542
- this.enumCache[column.key][_[key]] = {
543
- text: _[attr] ?? _[key],
544
- value: _[key],
545
- }
546
- })
547
539
 
548
- for(let key in this.enumCache[column.key]){
549
- if(!this.enumCache[column.key][key].text)
550
- this.enumCache[column.key][key].text = key
551
- }
540
+ for(let key in this.enumCache[column.key]){
541
+ if(!this.enumCache[column.key][key].text)
542
+ this.enumCache[column.key][key].text = key
543
+ }
552
544
 
553
- this.lastEnumItems = reqItems
554
- })
555
- .catch(err => this.log('Load enums error', err))
545
+ this.lastEnumItems = reqItems
556
546
  }
557
547
  }
558
- }
559
548
 
549
+ }))
560
550
  },
561
551
 
562
552
  loadExt(){
563
553
  if(!this.extBar.open) return
564
554
 
565
555
  this.readyState = 3
566
- return this.socket.send(this.src, {
556
+ return this.useSocket().send(this.computedSrc, {
567
557
  ...this.preset,
558
+ id: undefined,
559
+ uid: undefined,
568
560
  filters: [
569
561
  ...(this.preset.filters ?? []),
570
562
  ...(this.extBar.filters ?? [])
@@ -580,8 +572,10 @@ export default{
580
572
  loadExtNext(){
581
573
  if(!this.preset.filters) return
582
574
 
583
- return this.socket.send(this.src, {
575
+ return this.useSocket().send(this.computedSrc, {
584
576
  ...this.preset,
577
+ id: undefined,
578
+ uid: undefined,
585
579
  filters: [
586
580
  ...(this.preset.filters ?? []),
587
581
  ...(this.extBar.filters ?? [])
@@ -596,82 +590,27 @@ export default{
596
590
  },
597
591
 
598
592
  resize1(w){
599
- if(this.cConfig.sidebar.width + w >= 270 && this.cConfig.sidebar.width + w <= 600){
600
- this.cConfig.sidebar.width += w
593
+ if(this.sidebar.width + w >= 200 && this.sidebar.width + w <= 600){
594
+ this.sidebar.width += w
601
595
  }
602
596
  },
603
597
 
604
- selectPreset(uid){
605
- const preset = this.presets.find(_ => _.uid === uid)
606
-
607
- if(preset){
608
- this.cConfig.presetIdx = uid
598
+ selectPreset(idx){
599
+ if(this.configPresets[idx]){
600
+ this.configParams.presetIdx = this.configPresets[idx].uid
609
601
  this.load()
610
- this.$refs.presetSelector.close()
611
- this.$refs.contextMenu.close()
602
+ this.$refs.presetSelector?.close()
603
+ this.$refs.contextMenu?.close()
612
604
  }
613
605
  },
614
606
 
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
- if(save)
621
- this.saveConfig()
622
- },
623
-
624
- async loadConfig(){
625
- if(!this.presetKey || !this.presetSrc) return
626
-
627
- if(Object.keys(this.$route.query).map(_ => _.toLowerCase()).includes('reset')){
628
- await this.loadDefaultConfig(true)
629
-
630
- this.$router.replace({ query: {
631
- ...this.$route.query,
632
- reset: undefined
633
- }})
634
- }
635
- else{
636
- return this.socket.send(this.presetSrc, { key:this.presetKey })
637
- .then(async (config) => {
638
- !config ?
639
- await this.loadDefaultConfig() :
640
- this._config = config
641
- })
642
- .catch(() => {})
607
+ savePreset: invokeAfterIdle(function() {
608
+ if(this.presetKey) {
609
+ this.useSocket().send(this.presetSrc,
610
+ {key: this.presetKey, config: this.cConfig})
643
611
  }
644
- },
645
-
646
- saveConfig: invokeAfterIdle(function(){
647
- if(!this.presetKey || !this.presetSrc) return
648
-
649
- return this.socket.send(this.presetSrc, {
650
- key:this.presetKey,
651
- config: this.cConfig
652
- })
653
- .catch(() => {})
654
612
  }),
655
613
 
656
- async removePreset(preset){
657
- return this.socket.send(this.presetSrc, {
658
- key: this.presetKey,
659
- removePreset: preset
660
- })
661
- .catch(() => {})
662
- },
663
-
664
- async removeShared(uid){
665
- return this.socket.send(this.presetSrc, {
666
- key: this.presetKey,
667
- removeShared: [ uid ]
668
- })
669
- .then(_ => {
670
- this.sharedPresets.splice(this.sharedPresets.findIndex(_ => _.uid === uid), 1)
671
- })
672
- .catch(() => {})
673
- },
674
-
675
614
  openPreset(){
676
615
 
677
616
  switch(this.computedPresetMode){
@@ -704,30 +643,71 @@ export default{
704
643
  }
705
644
  },
706
645
 
707
- onSignal(event, items){
708
- if(this.pivotEnabled) return
709
- if(!Array.isArray(items) || items.length < 1) return
646
+ sortItems(items){
710
647
 
711
- switch(event){
648
+ if(typeof this.sorts === 'function'){}
649
+ else if((this.sorts ?? []).length > 0){
650
+ return (items ?? []).sort((a, b) => {
651
+ return sortsFn(a, b, this.sorts, 0)
652
+ })
653
+ }
654
+ else if((this.preset.sorts ?? []).length > 0){
655
+ return (items ?? []).sort((a, b) => {
656
+ return sortsFn(a, b, this.preset.sorts, 0)
657
+ })
658
+ }
659
+ return items
712
660
 
713
- case 'destroy':
714
- this.$util.remove(this.data.items, items)
715
- break
661
+ },
716
662
 
717
- default:
718
- const key = items[0] && items[0].uid ? 'uid' : 'id'
663
+ loadQueued(items){
664
+ const key = items[0] && items[0].uid ? 'uid' : 'id'
665
+
666
+ this.useSocket().send(this.computedSrc, {
667
+ ...this.preset,
668
+ id: undefined,
669
+ uid: undefined,
670
+ [key]: items.map(item => item[key]),
671
+ _signal: true
672
+ })
673
+ .then(({ items:nextItems }) => {
674
+
675
+ const destroyedItems = items.filter(_ => !nextItems.find(i => i[key] === _[key]))
676
+ this.$util.remove(this.data.items, destroyedItems)
677
+
678
+ let currentFirstItem = this.dataItems[0]
679
+ const sortedItems = this.sortItems([
680
+ ...nextItems,
681
+ currentFirstItem
682
+ ]
683
+ .filter(_ => _))
684
+
685
+ for(let item of sortedItems){
686
+ if(item === currentFirstItem) break
687
+ this.$util.unshift(this.data.items, item, { highlight: true })
688
+ }
719
689
 
720
- this.socket.send(this.src, {
721
- ...this.preset,
722
- [key]: items.map(item => item[key])
723
690
  })
724
- .then(({ items:nextItems }) => {
725
- nextItems.forEach(item => this.$util.unshift(this.data.items, item, { highlight: true }))
691
+ },
726
692
 
727
- const destroyedItems = items.filter(_ => !nextItems.find(i => i[key] === _[key]))
728
- this.$util.remove(this.data.items, destroyedItems)
729
- })
730
- break
693
+ onSignal(event, items){
694
+ if(this.pivotEnabled) return
695
+ if(!Array.isArray(items) || items.length < 1) return
696
+
697
+ if(typeof this.onSignalFn === 'function'){
698
+ this.onSignalFn(event, items)
699
+ }
700
+ else{
701
+ switch(event){
702
+
703
+ case 'destroy':
704
+ this.$util.remove(this.data.items, items)
705
+ break
706
+
707
+ default:
708
+ this.queue(items)
709
+ break
710
+ }
731
711
  }
732
712
 
733
713
  this.$emit('signal', event, items)
@@ -741,7 +721,7 @@ export default{
741
721
  for(let idx in this.preset.pivot.rows){
742
722
  const pivotRow = this.preset.pivot.rows[idx]
743
723
  const key = pivotRow.key
744
- const presetColumn = this.preset.params.columns.find(_ => _.key === key)
724
+ const presetColumn = this.preset.columns.find(_ => _.key === key)
745
725
  let value = item[pivotRow.key]
746
726
  switch(presetColumn.type){
747
727
 
@@ -826,7 +806,7 @@ export default{
826
806
  if(![ 'count', 'countDistinct', 'sum' ].includes(vv)){
827
807
 
828
808
  const key = kk.substring(1)
829
- const presetColumn = this.preset.params.columns.find(_ => _.key === key)
809
+ const presetColumn = this.preset.columns.find(_ => _.key === key)
830
810
  let value = vv
831
811
  switch(presetColumn.type){
832
812
 
@@ -879,6 +859,12 @@ export default{
879
859
  }
880
860
  },
881
861
 
862
+ onConnect(reconnect){
863
+ if(reconnect){
864
+ this.load()
865
+ }
866
+ },
867
+
882
868
  onKeyDown(e){
883
869
 
884
870
  if(e.altKey){
@@ -892,127 +878,266 @@ export default{
892
878
  default:
893
879
  if(e.code.startsWith('Digit')){
894
880
  const idx = parseInt(e.code.substring(5)) - 1
895
- if(this.presets[idx]) {
896
- this.selectPreset(this.presets[idx].uid)
881
+ if(this.configPresets[idx]) {
882
+ this.selectPreset(idx)
897
883
  }
898
884
  }
899
885
  break
900
886
  }
901
887
 
902
888
  }
903
- }
889
+ },
904
890
 
905
- },
891
+ freeze(key){
892
+ const freezed = this.columns.find(_ => _.freeze)
893
+ if(freezed){
894
+ delete freezed.freeze
895
+ }
906
896
 
907
- mounted(){
908
- this.$addResizeListener(this.$el, () => this.compPrefix = this.$util.calculateMediaPrefix(this.$el.clientWidth))
897
+ const column = this.columns.find(_ => _.key === key)
898
+ if(column){
899
+ column.freeze = true
900
+ }
901
+ },
909
902
 
910
- this.loadConfig()
911
- .then(() => {
912
- this.readyState = 1
913
- this.$nextTick(() => {
914
- this.calcItemsPerPage()
915
- this.load()
916
- this.loadExt()
917
- })
903
+ unfreeze(key){
904
+
905
+ const column = this.columns.find(_ => _.key === key)
906
+ if(column){
907
+ delete column.freeze
908
+ }
909
+ },
910
+
911
+ onVisibleChange({ inViewport, entry }){
912
+ if(inViewport){
913
+ this.$addResizeListener(this.$el, () => this.compPrefix = this.$util.calculateMediaPrefix(this.$el.clientWidth))
914
+
915
+ this.loadPreset()
916
+ .finally(() => {
917
+ this.readyState = 1
918
+ this.$nextTick(() => {
919
+ this.calcItemsPerPage()
920
+ this.load()
921
+ this.loadExt()
922
+ })
923
+ })
924
+
925
+ if(this.subscribeKey){
926
+ this.useSocket().send('user.subscribe', { name:this.subscribeKey })
927
+ this.useSocket().on(this.subscribeKey, this.onSignal)
928
+ }
929
+
930
+ window.addEventListener('keydown', this.onKeyDown)
931
+
932
+ this.queue = queueForLater({
933
+ pop: this.loadQueued,
934
+ delay: this.updateInterval
918
935
  })
919
936
 
920
- if(this.subscribeKey){
921
- this.socket.send('user.subscribe', { name:this.subscribeKey })
922
- this.socket.on(this.subscribeKey, this.onSignal)
937
+ this.useSocket().on('connect', this.onConnect)
938
+ }
939
+ },
940
+
941
+ },
942
+
943
+ components: {PresetBar, PresetSelector, VirtualGrid, VirtualTable},
944
+
945
+ unmounted() {
946
+ this.$removeResizeListener(this.$el)
947
+ window.removeEventListener('keydown', this.onKeyDown)
948
+
949
+ if(this.subscribeKey) {
950
+ this.useSocket().send('user.unsubscribe', {name: this.subscribeKey})
951
+ this.useSocket().off(this.subscribeKey, this.onSignal)
923
952
  }
924
953
 
925
- window.addEventListener('keydown', this.onKeyDown)
954
+ this.useSocket().off('connect', this.onConnect)
926
955
  },
927
956
 
928
- props: {
929
- presetKey: String,
930
- presetSrc: {
931
- type: String,
932
- default: 'user.preset'
957
+ computed: {
958
+
959
+ columns(){
960
+ if((this.preset.pivot ?? {}).enabled){
961
+ return this.preset.pivot.columns ?? []
962
+ }
963
+
964
+ for(let idx in this.preset.columns){
965
+ if(this.preset.columns[idx].defaultFormat){
966
+ this.preset.columns[idx].format = this.preset.columns[idx].defaultFormat
967
+ delete this.preset.columns[idx].defaultFormat
968
+ }
969
+ }
970
+
971
+ return this.preset.columns
933
972
  },
934
973
 
935
- subscribeKey: String,
936
- src: undefined,
937
- title: String,
938
- view: {
939
- type: String,
940
- validator(value) {
941
- return ['grid', 'table'].includes(value)
974
+ cConfig(){
975
+ return setupConfig(this.config ?? this._config)
976
+ },
977
+
978
+ contentSlots(){
979
+ const slots = {}
980
+ for(let key in this.$slots){
981
+ if(!key.startsWith('col-'))
982
+ slots[key] = this.$slots[key]
942
983
  }
984
+ return slots
943
985
  },
944
- gridColumn: [ Number, String ],
945
- gridClass: {
946
- type: String,
947
- default: "flex-1 md:p-3"
986
+
987
+ headerSlots(){
988
+ const slots = {}
989
+ for(let key in this.$slots){
990
+ if(key.startsWith('col-'))
991
+ slots[key] = this.$slots[key]
992
+ }
993
+ return slots
948
994
  },
949
- gridContainerClass: {
950
- type: String,
951
- default: "md:gap-5"
995
+
996
+ computedGridColumn(){
997
+ return this.$device.type === 'mobile' ? 1 :
998
+ (this.gridColumn ?? 3)
952
999
  },
953
- headerClass: String,
954
- presetMode: {
955
- type: String,
956
- default: "sidebar"
1000
+
1001
+ computedPresetMode(){
1002
+ return this.$device.type === 'mobile' ? 'popup' : this.presetMode
957
1003
  },
958
- containerClass: {
959
- type: String,
960
- default: "md:p-5 md:gap-3"
1004
+
1005
+ dataItems(){
1006
+ const items = this.sortItems(this.data.items)
1007
+
1008
+ if(typeof this.itemsFn === 'function'){
1009
+ return this.itemsFn(this.data.items)
1010
+ }
1011
+
1012
+ return items
961
1013
  },
962
- toolbar: {
963
- type: [ String, Boolean ],
964
- default: true
1014
+
1015
+ preset(){
1016
+ const preset = this.configPresets.find(_ => _.uid === this.configParams.presetIdx) ??
1017
+ this.configSharedPresets.find(_ => _.uid === this.configParams.presetIdx)
1018
+
1019
+ if(preset){
1020
+ if(this.$route.query?.search)
1021
+ preset.search = this.$route.query.search
1022
+ }
1023
+
1024
+ return preset
965
1025
  },
966
1026
 
967
- config: Object,
968
- defaultConfig: Function,
1027
+ presetSrc(){
1028
+ return this.controller ?
1029
+ `${this.controller}.preset` :
1030
+ 'user.preset'
1031
+ },
1032
+
1033
+ presetView(){
1034
+ return this.$device.type === 'mobile' ? 'grid' :
1035
+ (this.view ?? this.preset.view)
1036
+ },
1037
+
1038
+ configParams(){
1039
+ if(!this.cConfig.params)
1040
+ this.cConfig.params = {}
1041
+
1042
+ if(this.configPresets.length > 0 &&
1043
+ (!this.cConfig.params.presetIdx ||
1044
+ !this.configPresets.find(_ => _.uid === this.cConfig.params.presetIdx)))
1045
+ this.cConfig.params.presetIdx = this.configPresets[0].uid
1046
+
1047
+ return this.cConfig.params
1048
+ },
1049
+
1050
+ configPresets(){
1051
+ return this.cConfig.presets ?? []
1052
+ },
1053
+
1054
+ configSharedPresets(){
1055
+ return this.cConfig.sharedPresets ?? []
1056
+ },
1057
+
1058
+ canSearch(){
1059
+ return [ 1, true, 'true' ].includes(this.searchable) // this.preset.columns.some(_ => 'search' in _)
1060
+ },
1061
+
1062
+ sidebar(){
1063
+
1064
+ if(!this.configParams.sidebar || !('open' in this.configParams.sidebar))
1065
+ this.configParams.sidebar = {
1066
+ open: false,
1067
+ width: 360
1068
+ }
1069
+
1070
+ return this.configParams.sidebar
1071
+ },
1072
+
1073
+ sidebarStyle(){
1074
+ return {
1075
+ width: !this.sidebar.open ? 0 : this.sidebar.width + 'px'
1076
+ }
1077
+ },
1078
+
1079
+ extBar(){
1080
+ if(!this.cConfig.extbar)
1081
+ this.cConfig.extbar = {
1082
+ open: false,
1083
+ height: Math.round(window.innerHeight / 2.2)
1084
+ }
1085
+
1086
+ return this.cConfig.extbar
1087
+ },
1088
+
1089
+ extStyle(){
1090
+ return {
1091
+ height: this.extBar.height + "px"
1092
+ }
1093
+ },
1094
+
1095
+ pivotEnabled(){
1096
+ return ((this.preset ?? {}).pivot ?? {}).enabled
1097
+ },
1098
+
1099
+ computedSrc(){
1100
+ return this.src ?? `${this.controller}.load`
1101
+ },
1102
+
1103
+ freezeLeft(){
1104
+ return (this.preset?.columns).findIndex(_ => _.freeze) + 1
1105
+ }
969
1106
 
970
- sharingSrc: String,
971
- removeSharingSrc: String,
972
1107
  },
973
1108
 
974
- provide(){
1109
+ data(){
975
1110
  return {
976
- listStyle: this.$style,
977
- emitRoot: () => {}
1111
+ readyState: 0,
1112
+ data: {
1113
+ itemsPerPage: 16,
1114
+ },
1115
+ observer: null,
1116
+ compPrefix: '',
1117
+ enumCache: {},
1118
+ extItems: null,
1119
+ lastEnumItems: null,
1120
+ queue: null,
1121
+ _config: {
1122
+ columns:[],
1123
+ presets:[]
1124
+ }
978
1125
  }
979
1126
  },
980
1127
 
981
- unmounted() {
982
- this.$removeResizeListener(this.$el)
983
- window.removeEventListener('keydown', this.onKeyDown)
984
-
985
- if(this.subscribeKey) {
986
- this.socket.send('user.unsubscribe', {name: this.subscribeKey})
987
- this.socket.off(this.subscribeKey, this.onSignal)
1128
+ provide(){
1129
+ return {
1130
+ listStyle: this.$style,
1131
+ load: this.load
988
1132
  }
989
1133
  },
990
1134
 
991
1135
  watch: {
992
1136
 
993
- config(to){
994
- this.$nextTick(() => {
995
- this.calcItemsPerPage()
996
- this.load()
997
- this.loadExt()
998
- })
999
- },
1000
-
1001
1137
  cConfig: {
1002
1138
  deep: true,
1003
- handler(to, from){
1004
- if(Object.keys(from ?? {}).length > 0){
1005
- this.saveConfig()
1006
- }
1007
- }
1008
- },
1009
-
1010
- preset: {
1011
- deep: true,
1012
- handler(to, from){
1013
- if(to && to.uid === from?.uid){
1014
-
1015
- }
1139
+ handler(){
1140
+ this.savePreset()
1016
1141
  }
1017
1142
  },
1018
1143