@mixd-id/web-scaffold 0.2.240706 → 0.2.250801009

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 +79 -167
  11. package/src/components/Card.vue +235 -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 +8 -3
  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 +524 -403
  44. package/src/components/ListContextMenu.vue +88 -0
  45. package/src/components/ListItem.vue +5 -3
  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 +11 -17
  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 +11 -16
  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 +147 -67
  94. package/src/themes/default/index.js +83 -155
  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 +37 -29
  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 +112 -234
  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 +227 -116
  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 +693 -704
  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', 'socket', '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,56 @@ 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
+ }
468
+ else{
469
+ const defaultConfig = setupConfig(typeof this.defaultConfig === 'function' ? await this.defaultConfig() : {})
470
+ Object.assign(this.cConfig, defaultConfig)
471
+
472
+ return new Promise(resolve => resolve())
473
+ }
474
+ }
475
+ else{
476
+ if(this.defaultConfig){
477
+ const defaultConfig = setupConfig(await this.defaultConfig())
478
+ Object.assign(this.cConfig, defaultConfig)
479
+ }
480
+
481
+ return new Promise((resolve) => {
482
+ const query = {}
483
+ for(let key in this.$route.query){
484
+ if(key.toLowerCase() !== 'reset')
485
+ query[key] = this.$route.query[key]
486
+ }
487
+ this.$router.replace({
488
+ ...this.$route,
489
+ query
490
+ })
491
+
492
+ resolve()
493
+ })
494
+ }
495
+ },
496
+
497
+ async loadEnums(items){
498
+ if(!items) return
499
+
500
+ return Promise.all((this.preset.columns ?? []).map(async column => {
513
501
 
514
502
  if(column.enumSrc) {
515
503
 
@@ -527,7 +515,7 @@ export default{
527
515
  if(reqItems.length > 0 && reqItems !== this.lastEnumItems){
528
516
 
529
517
  const [ src, key, attr ] = column.enumSrc.split(':')
530
- this.socket.send(src, {
518
+ const res = await this.useSocket().send(src, {
531
519
  columns: [
532
520
  { key:attr, visible:true }
533
521
  ],
@@ -536,35 +524,35 @@ export default{
536
524
  key:key, value: [ { operator:'in', value:reqItems } ]
537
525
  }
538
526
  ]
527
+ });
528
+
529
+ (res.items ?? []).forEach(_ => {
530
+ this.enumCache[column.key][_[key]] = {
531
+ text: _[attr] ?? _[key],
532
+ value: _[key],
533
+ }
539
534
  })
540
- .then(res => {
541
- (res.items ?? []).forEach(_ => {
542
- this.enumCache[column.key][_[key]] = {
543
- text: _[attr] ?? _[key],
544
- value: _[key],
545
- }
546
- })
547
535
 
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
- }
536
+ for(let key in this.enumCache[column.key]){
537
+ if(!this.enumCache[column.key][key].text)
538
+ this.enumCache[column.key][key].text = key
539
+ }
552
540
 
553
- this.lastEnumItems = reqItems
554
- })
555
- .catch(err => this.log('Load enums error', err))
541
+ this.lastEnumItems = reqItems
556
542
  }
557
543
  }
558
- }
559
544
 
545
+ }))
560
546
  },
561
547
 
562
548
  loadExt(){
563
549
  if(!this.extBar.open) return
564
550
 
565
551
  this.readyState = 3
566
- return this.socket.send(this.src, {
552
+ return this.useSocket().send(this.computedSrc, {
567
553
  ...this.preset,
554
+ id: undefined,
555
+ uid: undefined,
568
556
  filters: [
569
557
  ...(this.preset.filters ?? []),
570
558
  ...(this.extBar.filters ?? [])
@@ -580,8 +568,10 @@ export default{
580
568
  loadExtNext(){
581
569
  if(!this.preset.filters) return
582
570
 
583
- return this.socket.send(this.src, {
571
+ return this.useSocket().send(this.computedSrc, {
584
572
  ...this.preset,
573
+ id: undefined,
574
+ uid: undefined,
585
575
  filters: [
586
576
  ...(this.preset.filters ?? []),
587
577
  ...(this.extBar.filters ?? [])
@@ -596,82 +586,27 @@ export default{
596
586
  },
597
587
 
598
588
  resize1(w){
599
- if(this.cConfig.sidebar.width + w >= 270 && this.cConfig.sidebar.width + w <= 600){
600
- this.cConfig.sidebar.width += w
589
+ if(this.sidebar.width + w >= 200 && this.sidebar.width + w <= 600){
590
+ this.sidebar.width += w
601
591
  }
602
592
  },
603
593
 
604
- selectPreset(uid){
605
- const preset = this.presets.find(_ => _.uid === uid)
606
-
607
- if(preset){
608
- this.cConfig.presetIdx = uid
594
+ selectPreset(idx){
595
+ if(this.configPresets[idx]){
596
+ this.configParams.presetIdx = this.configPresets[idx].uid
609
597
  this.load()
610
- this.$refs.presetSelector.close()
611
- this.$refs.contextMenu.close()
598
+ this.$refs.presetSelector?.close()
599
+ this.$refs.contextMenu?.close()
612
600
  }
613
601
  },
614
602
 
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(() => {})
603
+ savePreset: invokeAfterIdle(function() {
604
+ if(this.presetKey) {
605
+ this.useSocket().send(this.presetSrc,
606
+ {key: this.presetKey, config: this.cConfig})
643
607
  }
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
608
  }),
655
609
 
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
610
  openPreset(){
676
611
 
677
612
  switch(this.computedPresetMode){
@@ -704,30 +639,71 @@ export default{
704
639
  }
705
640
  },
706
641
 
707
- onSignal(event, items){
708
- if(this.pivotEnabled) return
709
- if(!Array.isArray(items) || items.length < 1) return
642
+ sortItems(items){
710
643
 
711
- switch(event){
644
+ if(typeof this.sorts === 'function'){}
645
+ else if((this.sorts ?? []).length > 0){
646
+ return (items ?? []).sort((a, b) => {
647
+ return sortsFn(a, b, this.sorts, 0)
648
+ })
649
+ }
650
+ else if((this.preset.sorts ?? []).length > 0){
651
+ return (items ?? []).sort((a, b) => {
652
+ return sortsFn(a, b, this.preset.sorts, 0)
653
+ })
654
+ }
655
+ return items
712
656
 
713
- case 'destroy':
714
- this.$util.remove(this.data.items, items)
715
- break
657
+ },
716
658
 
717
- default:
718
- const key = items[0] && items[0].uid ? 'uid' : 'id'
659
+ loadQueued(items){
660
+ const key = items[0] && items[0].uid ? 'uid' : 'id'
661
+
662
+ this.useSocket().send(this.computedSrc, {
663
+ ...this.preset,
664
+ id: undefined,
665
+ uid: undefined,
666
+ [key]: items.map(item => item[key]),
667
+ _signal: true
668
+ })
669
+ .then(({ items:nextItems }) => {
670
+
671
+ const destroyedItems = items.filter(_ => !nextItems.find(i => i[key] === _[key]))
672
+ this.$util.remove(this.data.items, destroyedItems)
673
+
674
+ let currentFirstItem = this.dataItems[0]
675
+ const sortedItems = this.sortItems([
676
+ ...nextItems,
677
+ currentFirstItem
678
+ ]
679
+ .filter(_ => _))
680
+
681
+ for(let item of sortedItems){
682
+ if(item === currentFirstItem) break
683
+ this.$util.unshift(this.data.items, item, { highlight: true })
684
+ }
719
685
 
720
- this.socket.send(this.src, {
721
- ...this.preset,
722
- [key]: items.map(item => item[key])
723
686
  })
724
- .then(({ items:nextItems }) => {
725
- nextItems.forEach(item => this.$util.unshift(this.data.items, item, { highlight: true }))
687
+ },
726
688
 
727
- const destroyedItems = items.filter(_ => !nextItems.find(i => i[key] === _[key]))
728
- this.$util.remove(this.data.items, destroyedItems)
729
- })
730
- break
689
+ onSignal(event, items){
690
+ if(this.pivotEnabled) return
691
+ if(!Array.isArray(items) || items.length < 1) return
692
+
693
+ if(typeof this.onSignalFn === 'function'){
694
+ this.onSignalFn(event, items)
695
+ }
696
+ else{
697
+ switch(event){
698
+
699
+ case 'destroy':
700
+ this.$util.remove(this.data.items, items)
701
+ break
702
+
703
+ default:
704
+ this.queue(items)
705
+ break
706
+ }
731
707
  }
732
708
 
733
709
  this.$emit('signal', event, items)
@@ -741,7 +717,7 @@ export default{
741
717
  for(let idx in this.preset.pivot.rows){
742
718
  const pivotRow = this.preset.pivot.rows[idx]
743
719
  const key = pivotRow.key
744
- const presetColumn = this.preset.params.columns.find(_ => _.key === key)
720
+ const presetColumn = this.preset.columns.find(_ => _.key === key)
745
721
  let value = item[pivotRow.key]
746
722
  switch(presetColumn.type){
747
723
 
@@ -826,7 +802,7 @@ export default{
826
802
  if(![ 'count', 'countDistinct', 'sum' ].includes(vv)){
827
803
 
828
804
  const key = kk.substring(1)
829
- const presetColumn = this.preset.params.columns.find(_ => _.key === key)
805
+ const presetColumn = this.preset.columns.find(_ => _.key === key)
830
806
  let value = vv
831
807
  switch(presetColumn.type){
832
808
 
@@ -879,6 +855,12 @@ export default{
879
855
  }
880
856
  },
881
857
 
858
+ onConnect(reconnect){
859
+ if(reconnect){
860
+ this.load()
861
+ }
862
+ },
863
+
882
864
  onKeyDown(e){
883
865
 
884
866
  if(e.altKey){
@@ -892,127 +874,266 @@ export default{
892
874
  default:
893
875
  if(e.code.startsWith('Digit')){
894
876
  const idx = parseInt(e.code.substring(5)) - 1
895
- if(this.presets[idx]) {
896
- this.selectPreset(this.presets[idx].uid)
877
+ if(this.configPresets[idx]) {
878
+ this.selectPreset(idx)
897
879
  }
898
880
  }
899
881
  break
900
882
  }
901
883
 
902
884
  }
903
- }
885
+ },
904
886
 
905
- },
887
+ freeze(key){
888
+ const freezed = this.columns.find(_ => _.freeze)
889
+ if(freezed){
890
+ delete freezed.freeze
891
+ }
906
892
 
907
- mounted(){
908
- this.$addResizeListener(this.$el, () => this.compPrefix = this.$util.calculateMediaPrefix(this.$el.clientWidth))
893
+ const column = this.columns.find(_ => _.key === key)
894
+ if(column){
895
+ column.freeze = true
896
+ }
897
+ },
909
898
 
910
- this.loadConfig()
911
- .then(() => {
912
- this.readyState = 1
913
- this.$nextTick(() => {
914
- this.calcItemsPerPage()
915
- this.load()
916
- this.loadExt()
917
- })
899
+ unfreeze(key){
900
+
901
+ const column = this.columns.find(_ => _.key === key)
902
+ if(column){
903
+ delete column.freeze
904
+ }
905
+ },
906
+
907
+ onVisibleChange({ inViewport, entry }){
908
+ if(inViewport){
909
+ this.$addResizeListener(this.$el, () => this.compPrefix = this.$util.calculateMediaPrefix(this.$el.clientWidth))
910
+
911
+ this.loadPreset()
912
+ .finally(() => {
913
+ this.readyState = 1
914
+ this.$nextTick(() => {
915
+ this.calcItemsPerPage()
916
+ this.load()
917
+ this.loadExt()
918
+ })
919
+ })
920
+
921
+ if(this.subscribeKey){
922
+ this.useSocket().send('user.subscribe', { name:this.subscribeKey })
923
+ this.useSocket().on(this.subscribeKey, this.onSignal)
924
+ }
925
+
926
+ window.addEventListener('keydown', this.onKeyDown)
927
+
928
+ this.queue = queueForLater({
929
+ pop: this.loadQueued,
930
+ delay: this.updateInterval
918
931
  })
919
932
 
920
- if(this.subscribeKey){
921
- this.socket.send('user.subscribe', { name:this.subscribeKey })
922
- this.socket.on(this.subscribeKey, this.onSignal)
933
+ this.useSocket().on('connect', this.onConnect)
934
+ }
935
+ },
936
+
937
+ },
938
+
939
+ components: {PresetBar, PresetSelector, VirtualGrid, VirtualTable},
940
+
941
+ unmounted() {
942
+ this.$removeResizeListener(this.$el)
943
+ window.removeEventListener('keydown', this.onKeyDown)
944
+
945
+ if(this.subscribeKey) {
946
+ this.useSocket().send('user.unsubscribe', {name: this.subscribeKey})
947
+ this.useSocket().off(this.subscribeKey, this.onSignal)
923
948
  }
924
949
 
925
- window.addEventListener('keydown', this.onKeyDown)
950
+ this.useSocket().off('connect', this.onConnect)
926
951
  },
927
952
 
928
- props: {
929
- presetKey: String,
930
- presetSrc: {
931
- type: String,
932
- default: 'user.preset'
953
+ computed: {
954
+
955
+ columns(){
956
+ if((this.preset.pivot ?? {}).enabled){
957
+ return this.preset.pivot.columns ?? []
958
+ }
959
+
960
+ for(let idx in this.preset.columns){
961
+ if(this.preset.columns[idx].defaultFormat){
962
+ this.preset.columns[idx].format = this.preset.columns[idx].defaultFormat
963
+ delete this.preset.columns[idx].defaultFormat
964
+ }
965
+ }
966
+
967
+ return this.preset.columns
933
968
  },
934
969
 
935
- subscribeKey: String,
936
- src: undefined,
937
- title: String,
938
- view: {
939
- type: String,
940
- validator(value) {
941
- return ['grid', 'table'].includes(value)
970
+ cConfig(){
971
+ return setupConfig(this.config ?? this._config)
972
+ },
973
+
974
+ contentSlots(){
975
+ const slots = {}
976
+ for(let key in this.$slots){
977
+ if(!key.startsWith('col-'))
978
+ slots[key] = this.$slots[key]
942
979
  }
980
+ return slots
943
981
  },
944
- gridColumn: [ Number, String ],
945
- gridClass: {
946
- type: String,
947
- default: "flex-1 md:p-3"
982
+
983
+ headerSlots(){
984
+ const slots = {}
985
+ for(let key in this.$slots){
986
+ if(key.startsWith('col-'))
987
+ slots[key] = this.$slots[key]
988
+ }
989
+ return slots
948
990
  },
949
- gridContainerClass: {
950
- type: String,
951
- default: "md:gap-5"
991
+
992
+ computedGridColumn(){
993
+ return this.$device.type === 'mobile' ? 1 :
994
+ (this.gridColumn ?? 3)
952
995
  },
953
- headerClass: String,
954
- presetMode: {
955
- type: String,
956
- default: "sidebar"
996
+
997
+ computedPresetMode(){
998
+ return this.$device.type === 'mobile' ? 'popup' : this.presetMode
957
999
  },
958
- containerClass: {
959
- type: String,
960
- default: "md:p-5 md:gap-3"
1000
+
1001
+ dataItems(){
1002
+ const items = this.sortItems(this.data.items)
1003
+
1004
+ if(typeof this.itemsFn === 'function'){
1005
+ return this.itemsFn(this.data.items)
1006
+ }
1007
+
1008
+ return items
961
1009
  },
962
- toolbar: {
963
- type: [ String, Boolean ],
964
- default: true
1010
+
1011
+ preset(){
1012
+ const preset = this.configPresets.find(_ => _.uid === this.configParams.presetIdx) ??
1013
+ this.configSharedPresets.find(_ => _.uid === this.configParams.presetIdx)
1014
+
1015
+ if(preset){
1016
+ if(this.$route.query?.search)
1017
+ preset.search = this.$route.query.search
1018
+ }
1019
+
1020
+ return preset
965
1021
  },
966
1022
 
967
- config: Object,
968
- defaultConfig: Function,
1023
+ presetSrc(){
1024
+ return this.controller ?
1025
+ `${this.controller}.preset` :
1026
+ 'user.preset'
1027
+ },
1028
+
1029
+ presetView(){
1030
+ return this.$device.type === 'mobile' ? 'grid' :
1031
+ (this.view ?? this.preset.view)
1032
+ },
1033
+
1034
+ configParams(){
1035
+ if(!this.cConfig.params)
1036
+ this.cConfig.params = {}
1037
+
1038
+ if(this.configPresets.length > 0 &&
1039
+ (!this.cConfig.params.presetIdx ||
1040
+ !this.configPresets.find(_ => _.uid === this.cConfig.params.presetIdx)))
1041
+ this.cConfig.params.presetIdx = this.configPresets[0].uid
1042
+
1043
+ return this.cConfig.params
1044
+ },
1045
+
1046
+ configPresets(){
1047
+ return this.cConfig.presets ?? []
1048
+ },
1049
+
1050
+ configSharedPresets(){
1051
+ return this.cConfig.sharedPresets ?? []
1052
+ },
1053
+
1054
+ canSearch(){
1055
+ return [ 1, true, 'true' ].includes(this.searchable) // this.preset.columns.some(_ => 'search' in _)
1056
+ },
1057
+
1058
+ sidebar(){
1059
+
1060
+ if(!this.configParams.sidebar || !('open' in this.configParams.sidebar))
1061
+ this.configParams.sidebar = {
1062
+ open: false,
1063
+ width: 360
1064
+ }
1065
+
1066
+ return this.configParams.sidebar
1067
+ },
1068
+
1069
+ sidebarStyle(){
1070
+ return {
1071
+ width: !this.sidebar.open ? 0 : this.sidebar.width + 'px'
1072
+ }
1073
+ },
1074
+
1075
+ extBar(){
1076
+ if(!this.cConfig.extbar)
1077
+ this.cConfig.extbar = {
1078
+ open: false,
1079
+ height: Math.round(window.innerHeight / 2.2)
1080
+ }
1081
+
1082
+ return this.cConfig.extbar
1083
+ },
1084
+
1085
+ extStyle(){
1086
+ return {
1087
+ height: this.extBar.height + "px"
1088
+ }
1089
+ },
1090
+
1091
+ pivotEnabled(){
1092
+ return ((this.preset ?? {}).pivot ?? {}).enabled
1093
+ },
1094
+
1095
+ computedSrc(){
1096
+ return this.src ?? `${this.controller}.load`
1097
+ },
1098
+
1099
+ freezeLeft(){
1100
+ return (this.preset?.columns).findIndex(_ => _.freeze) + 1
1101
+ }
969
1102
 
970
- sharingSrc: String,
971
- removeSharingSrc: String,
972
1103
  },
973
1104
 
974
- provide(){
1105
+ data(){
975
1106
  return {
976
- listStyle: this.$style,
977
- emitRoot: () => {}
1107
+ readyState: 0,
1108
+ data: {
1109
+ itemsPerPage: 16,
1110
+ },
1111
+ observer: null,
1112
+ compPrefix: '',
1113
+ enumCache: {},
1114
+ extItems: null,
1115
+ lastEnumItems: null,
1116
+ queue: null,
1117
+ _config: {
1118
+ columns:[],
1119
+ presets:[]
1120
+ }
978
1121
  }
979
1122
  },
980
1123
 
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)
1124
+ provide(){
1125
+ return {
1126
+ listStyle: this.$style,
1127
+ load: this.load
988
1128
  }
989
1129
  },
990
1130
 
991
1131
  watch: {
992
1132
 
993
- config(to){
994
- this.$nextTick(() => {
995
- this.calcItemsPerPage()
996
- this.load()
997
- this.loadExt()
998
- })
999
- },
1000
-
1001
1133
  cConfig: {
1002
1134
  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
- }
1135
+ handler(){
1136
+ this.savePreset()
1016
1137
  }
1017
1138
  },
1018
1139