@mixd-id/web-scaffold 0.1.230406124 → 0.1.230406126

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mixd-id/web-scaffold",
3
3
  "private": false,
4
- "version": "0.1.230406124",
4
+ "version": "0.1.230406126",
5
5
  "scripts": {
6
6
  "dev": "vite serve",
7
7
  "build": "vite build",
@@ -1,167 +1,209 @@
1
1
  <template>
2
- <div :class="$style.comp">
3
-
4
- <slot v-if="$slots.head" name="head"></slot>
5
- <div v-else class="flex flex-row items-center gap-4 px-6 md:px-0 py-4 md:py-0 bg-base-400 dark:bg-base-300 md:bg-transparent">
6
- <div class="flex-1 flex flex-row gap-6">
7
- <button type="button" ref="presetSelectorBtn"
8
- class="flex-1 md:flex-none flex flex-row gap-1 items-center text-left md:ml-2"
9
- @click="$refs.presetSelector.toggle($refs.presetSelectorBtn)">
10
- <h2 class="overflow-hidden whitespace-nowrap text-ellipsis">{{ preset.name }}</h2>
11
- <svg width="13" height="13" class="ml-1 relative top-[2px] fill-text hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"/></svg>
12
- </button>
13
-
14
- <ContextMenu ref="presetSelector">
15
- <div class="flex-1 flex flex-col gap-4 md:w-[270px] cursor-pointer p-6">
16
- <div class="pb-0 flex flex-row gap-4">
17
- <h4 class="flex-1">Select Preset</h4>
18
- <button type="button" class="text-primary" @click="openPreset">Settings</button>
2
+ <Suspense>
3
+ <div :class="$style.comp" v-if="configLoaded">
4
+
5
+ <slot v-if="$slots.head" name="head"></slot>
6
+ <div v-else :class="$style.toolbar">
7
+ <div class="flex-1 flex flex-row gap-6">
8
+ <button type="button" ref="presetSelectorBtn"
9
+ class="flex-1 md:flex-none flex flex-col text-left md:ml-2 leading-tight"
10
+ @click="$refs.presetSelector.toggle($refs.presetSelectorBtn)">
11
+ <small v-if="title" class="text-text-400 text-xs">{{ title }}</small>
12
+ <div class="flex flex-row items-center">
13
+ <h3 class="overflow-hidden whitespace-nowrap text-ellipsis">{{ preset.name }}</h3>
14
+ <svg width="13" height="13" class="ml-1 relative top-[2px] fill-text hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"/></svg>
19
15
  </div>
20
- <div class="border-text-50 border-[1px] divide-y divide-text-50 bg-base-400 rounded-lg overflow-hidden">
21
- <button type="button" v-for="(preset, idx) in config.presets"
22
- class="p-3 text-left hover:bg-primary hover:text-white block w-full"
23
- @click="selectPreset(idx)">
24
- {{ preset.name }}
25
- </button>
26
- </div>
27
- </div>
28
- </ContextMenu>
29
-
30
- <div class="absolute">
31
- <Modal ref="setting" width="680" height="560" position="bottom">
32
- <template #head>
33
- <div class="relative">
34
- <div class="absolute top-0 right-0 p-2 py-3">
35
- <button type="button" class="p-2" @click="$refs.setting.close()">
36
- <svg width="24" height="24" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
37
- <path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
38
- </svg>
39
- </button>
40
- </div>
16
+ </button>
17
+
18
+ <ContextMenu ref="presetSelector">
19
+ <div class="flex-1 flex flex-col gap-4 md:w-[270px] cursor-pointer p-6">
20
+ <div class="pb-0 flex flex-row gap-4">
21
+ <h4 class="flex-1">Select Preset</h4>
22
+ <button type="button" class="text-primary" @click="openPreset">Settings</button>
41
23
  </div>
42
- </template>
43
- <template #foot>
44
- <div class="p-6 py-4 border-t-[1px] border-text-50">
45
- <Button class="w-full md:w-[90px]" @click="applyPreset">Apply</Button>
24
+ <div class="border-text-50 border-[1px] divide-y divide-text-50 bg-base-400 rounded-lg overflow-hidden">
25
+ <button type="button" v-for="(preset, idx) in config.presets"
26
+ class="p-3 text-left hover:bg-primary hover:text-white block w-full"
27
+ @click="selectPreset(idx)">
28
+ {{ preset.name }}
29
+ </button>
46
30
  </div>
47
- </template>
48
- <ListViewSettings ref="listviewSettings" class="flex-1"
49
- :config="copiedConfig" />
50
- </Modal>
31
+ </div>
32
+ </ContextMenu>
33
+
34
+ <div class="absolute">
35
+ <Modal ref="setting" width="680" height="560" position="bottom">
36
+ <template #head>
37
+ <div class="relative">
38
+ <div class="absolute top-0 right-0 p-2 py-3">
39
+ <button type="button" class="p-2" @click="$refs.setting.close()">
40
+ <svg width="24" height="24" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
41
+ <path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
42
+ </svg>
43
+ </button>
44
+ </div>
45
+ </div>
46
+ </template>
47
+ <template #foot>
48
+ <div class="p-6 py-4 border-t-[1px] border-text-50">
49
+ <Button class="w-full md:w-[90px]" @click="applyPreset">Apply</Button>
50
+ </div>
51
+ </template>
52
+ <ListViewSettings ref="listviewSettings" class="flex-1"
53
+ :config="copiedConfig" />
54
+ </Modal>
55
+ </div>
51
56
  </div>
52
- </div>
53
57
 
54
- <slot name="headerOpt"></slot>
58
+ <slot name="headerOpt"></slot>
55
59
 
56
- <Textbox v-if="mediaPrefix && mediaPrefix !== 'sm'" :placeholder="$t('Search...')" :clearable="true"
57
- @clear="clearSearch" v-model="preset.search"
58
- @keyup.enter="load" :class="$style.searchBox">
59
- <template #start>
60
- <div class="pl-2">
61
- <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M508.5 481.6l-129-129c-2.3-2.3-5.3-3.5-8.5-3.5h-10.3C395 312 416 262.5 416 208 416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c54.5 0 104-21 141.1-55.2V371c0 3.2 1.3 6.2 3.5 8.5l129 129c4.7 4.7 12.3 4.7 17 0l9.9-9.9c4.7-4.7 4.7-12.3 0-17zM208 384c-97.3 0-176-78.7-176-176S110.7 32 208 32s176 78.7 176 176-78.7 176-176 176z"/></svg>
62
- </div>
63
- </template>
64
- </Textbox>
65
- </div>
60
+ <Textbox v-if="$screenPrefix.value !== ''" :placeholder="$t('Search...')" :clearable="true"
61
+ @clear="clearSearch" v-model="preset.search"
62
+ @keyup.enter="load" :class="$style.searchBox">
63
+ <template #start>
64
+ <div class="pl-2">
65
+ <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M508.5 481.6l-129-129c-2.3-2.3-5.3-3.5-8.5-3.5h-10.3C395 312 416 262.5 416 208 416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c54.5 0 104-21 141.1-55.2V371c0 3.2 1.3 6.2 3.5 8.5l129 129c4.7 4.7 12.3 4.7 17 0l9.9-9.9c4.7-4.7 4.7-12.3 0-17zM208 384c-97.3 0-176-78.7-176-176S110.7 32 208 32s176 78.7 176 176-78.7 176-176 176z"/></svg>
66
+ </div>
67
+ </template>
68
+ </Textbox>
69
+
70
+ <div v-if="$screenPrefix.value !== ''">
71
+ <button type="button" class="p-2" @click="changeViewType('grid')" v-tooltip="'Grid'">
72
+ <svg width="16" height="16" :class="computedViewType === 'grid' ? 'fill-primary' : 'fill-text-300 hover:fill-primary'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M496 424H304a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zm0-128H304a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zm0-128H304a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zm0-128H304a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16V56a16 16 0 0 0-16-16zM208 296H16a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zm0 128H16a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zm0-384H16A16 16 0 0 0 0 56v16a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16V56a16 16 0 0 0-16-16zm0 128H16a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16z"/></svg>
73
+ </button>
74
+ <button type="button" class="p-2" @click="changeViewType('table')" v-tooltip="'Table'">
75
+ <svg width="16" height="16" :class="computedViewType === 'table' ? 'fill-primary' : 'fill-text-300 hover:fill-primary'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M464 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V80c0-26.51-21.49-48-48-48zM232 432H54a6 6 0 0 1-6-6V296h184v136zm0-184H48V112h184v136zm226 184H280V296h184v130a6 6 0 0 1-6 6zm6-184H280V112h184v136z"/></svg>
76
+ </button>
77
+ </div>
78
+ </div>
66
79
 
67
- <div v-if="mediaPrefix && mediaPrefix === 'sm'" class="px-6 pb-4 border-b-[1px] border-text-50 bg-base-400 dark:bg-base-300">
68
- <Textbox :placeholder="$t('Search...')" :clearable="true" @clear="clearSearch" v-model="preset.search"
69
- @keyup.enter="load" :class="$style.searchBox2">
70
- <template #start>
71
- <div class="pl-2">
72
- <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M508.5 481.6l-129-129c-2.3-2.3-5.3-3.5-8.5-3.5h-10.3C395 312 416 262.5 416 208 416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c54.5 0 104-21 141.1-55.2V371c0 3.2 1.3 6.2 3.5 8.5l129 129c4.7 4.7 12.3 4.7 17 0l9.9-9.9c4.7-4.7 4.7-12.3 0-17zM208 384c-97.3 0-176-78.7-176-176S110.7 32 208 32s176 78.7 176 176-78.7 176-176 176z"/></svg>
73
- </div>
74
- </template>
75
- </Textbox>
76
- </div>
80
+ <div v-if="$screenPrefix.value === ''" class="px-6 pb-4 border-b-[1px] border-text-50 bg-base-400 dark:bg-base-300">
81
+ <Textbox :placeholder="$t('Search...')" :clearable="true" @clear="clearSearch" v-model="preset.search"
82
+ @keyup.enter="load" :class="$style.searchBox2">
83
+ <template #start>
84
+ <div class="pl-2">
85
+ <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M508.5 481.6l-129-129c-2.3-2.3-5.3-3.5-8.5-3.5h-10.3C395 312 416 262.5 416 208 416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c54.5 0 104-21 141.1-55.2V371c0 3.2 1.3 6.2 3.5 8.5l129 129c4.7 4.7 12.3 4.7 17 0l9.9-9.9c4.7-4.7 4.7-12.3 0-17zM208 384c-97.3 0-176-78.7-176-176S110.7 32 208 32s176 78.7 176 176-78.7 176-176 176z"/></svg>
86
+ </div>
87
+ </template>
88
+ </Textbox>
89
+ </div>
77
90
 
78
- <div v-if="presetSummary && summary" class="h-[300px] max-h-[30vh] flex">
79
- <Bar v-if="presetSummary.mode === 'bar'" :data="chartData" :options="chartOptions" class="flex-1 w-full p-2"/>
80
- <Line v-else-if="presetSummary.mode === 'line'" :data="chartData" :options="chartOptions" class="flex-1 w-full p-2"/>
81
- <VirtualTable v-else-if="presetSummary.mode === 'table'" :items="summaryItems" :columns="summaryColumns" class="flex-1"></VirtualTable>
82
- <Gmaps v-else-if="presetSummary.mode === 'map'" :data="summary.items" :config="presetSummary.map"
83
- class="flex-1 w-full p-2" :apiKey="mapApiKey" />
84
- </div>
91
+ <div v-if="presetSummary && summary" class="h-[300px] max-h-[30vh] flex">
92
+ <Bar v-if="presetSummary.mode === 'bar'" :data="chartData" :options="chartOptions" class="flex-1 w-full p-2"/>
93
+ <Line v-else-if="presetSummary.mode === 'line'" :data="chartData" :options="chartOptions" class="flex-1 w-full p-2"/>
94
+ <VirtualTable v-else-if="presetSummary.mode === 'table'" :items="summaryItems" :columns="summaryColumns" class="flex-1"></VirtualTable>
95
+ <Gmaps v-else-if="presetSummary.mode === 'map'" :data="summary.items" :config="presetSummary.map"
96
+ class="flex-1 w-full p-2" :apiKey="mapApiKey" />
97
+ </div>
85
98
 
86
- <div class="flex-1 flex" v-if="mediaPrefix">
87
- <VirtualScroll v-if="mediaPrefix === 'sm'" :items="items" ref="table2"
88
- class="flex-1 bg-base-400 dark:bg-base-300" @scroll-end="loadNext">
89
- <template #item="{ item }">
90
- <slot name="mobileItem" :item="item"></slot>
91
- </template>
92
- </VirtualScroll>
93
-
94
- <VirtualTable v-else ref="table1" :columns="presetColumns" :items="items"
95
- class="flex-1 bg-base-400"
96
- @scroll-end="loadNext">
97
- <template v-for="column in presetColumns" #[colOf(column.key)]="{}">
98
- <div :class="getHeader(column)" @click="openColumnOptions(column.key, $event.target.closest('.' + $style.header))">
99
- <div>
100
- {{ column.label ?? column.key }}
101
- </div>
102
- <div class="absolute top-0 right-0 p-2 bg-base-500" v-if="presetSortedColumns[column.key] === 'asc'">
103
- <svg width="19" height="19" viewBox="0 0 24 24" class="fill-text-400" xmlns="http://www.w3.org/2000/svg">
104
- <path d="M6.75 5C6.75 4.58579 6.41421 4.25 6 4.25C5.58579 4.25 5.25 4.58579 5.25 5V17.6893L3.53033 15.9697C3.23744 15.6768 2.76256 15.6768 2.46967 15.9697C2.17678 16.2626 2.17678 16.7374 2.46967 17.0303L4.76256 19.3232C5.44598 20.0066 6.55402 20.0066 7.23744 19.3232L9.53033 17.0303C9.82322 16.7374 9.82322 16.2626 9.53033 15.9697C9.23744 15.6768 8.76256 15.6768 8.46967 15.9697L6.75 17.6893V5Z"/>
105
- <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 17C12.25 17.4142 12.5858 17.75 13 17.75H21C21.4142 17.75 21.75 17.4142 21.75 17C21.75 16.5858 21.4142 16.25 21 16.25H13C12.5858 16.25 12.25 16.5858 12.25 17Z"/>
106
- <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 12C12.25 11.5858 12.5858 11.25 13 11.25H18C18.4142 11.25 18.75 11.5858 18.75 12C18.75 12.4142 18.4142 12.75 18 12.75H13C12.5858 12.75 12.25 12.4142 12.25 12Z"/>
107
- <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 7C12.25 6.58579 12.5858 6.25 13 6.25H15C15.4142 6.25 15.75 6.58579 15.75 7C15.75 7.41421 15.4142 7.75 15 7.75H13C12.5858 7.75 12.25 7.41421 12.25 7Z"/>
108
- </svg>
99
+ <div class="flex-1 flex">
100
+ <VirtualScroll v-if="computedViewType === 'feed'"
101
+ :items="items"
102
+ ref="table1"
103
+ class="flex-1"
104
+ @scroll-end="loadNext">
105
+ <template #item="{ item }">
106
+ <slot name="mobileItem" :item="item">
107
+ <div :class="$style.mobileItem">
108
+ <strong>{{ item.name }}</strong>
109
+ </div>
110
+ </slot>
111
+ </template>
112
+ </VirtualScroll>
113
+
114
+ <VirtualGrid v-else-if="computedViewType === 'grid'"
115
+ :items="items"
116
+ :column="gridColumn"
117
+ :gap="gridGap"
118
+ class="flex-1"
119
+ ref="table1"
120
+ :opt-bar="optBar"
121
+ @scroll-end="loadNext"
122
+ :config="config">
123
+ <template #item="{ item }">
124
+ <slot name="gridItem" :item="item">
125
+ <div :class="$style.mobileItem">
126
+ <strong>{{ item.name }}</strong>
127
+ </div>
128
+ </slot>
129
+ </template>
130
+ </VirtualGrid>
131
+
132
+ <VirtualTable v-else-if="computedViewType === 'table'"
133
+ ref="table1"
134
+ :columns="presetColumns"
135
+ :items="items"
136
+ class="flex-1 bg-base-400"
137
+ @scroll-end="loadNext">
138
+ <template v-for="column in presetColumns" #[colOf(column.key)]="{}">
139
+ <div :class="getHeader(column)" @click="openColumnOptions(column.key, $event.target.closest('.' + $style.header))">
140
+ <div>
141
+ {{ column.label ?? column.key }}
142
+ </div>
143
+ <div class="absolute top-0 right-0 p-2 bg-base-500" v-if="presetSortedColumns[column.key] === 'asc'">
144
+ <svg width="19" height="19" viewBox="0 0 24 24" class="fill-text-400" xmlns="http://www.w3.org/2000/svg">
145
+ <path d="M6.75 5C6.75 4.58579 6.41421 4.25 6 4.25C5.58579 4.25 5.25 4.58579 5.25 5V17.6893L3.53033 15.9697C3.23744 15.6768 2.76256 15.6768 2.46967 15.9697C2.17678 16.2626 2.17678 16.7374 2.46967 17.0303L4.76256 19.3232C5.44598 20.0066 6.55402 20.0066 7.23744 19.3232L9.53033 17.0303C9.82322 16.7374 9.82322 16.2626 9.53033 15.9697C9.23744 15.6768 8.76256 15.6768 8.46967 15.9697L6.75 17.6893V5Z"/>
146
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 17C12.25 17.4142 12.5858 17.75 13 17.75H21C21.4142 17.75 21.75 17.4142 21.75 17C21.75 16.5858 21.4142 16.25 21 16.25H13C12.5858 16.25 12.25 16.5858 12.25 17Z"/>
147
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 12C12.25 11.5858 12.5858 11.25 13 11.25H18C18.4142 11.25 18.75 11.5858 18.75 12C18.75 12.4142 18.4142 12.75 18 12.75H13C12.5858 12.75 12.25 12.4142 12.25 12Z"/>
148
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 7C12.25 6.58579 12.5858 6.25 13 6.25H15C15.4142 6.25 15.75 6.58579 15.75 7C15.75 7.41421 15.4142 7.75 15 7.75H13C12.5858 7.75 12.25 7.41421 12.25 7Z"/>
149
+ </svg>
150
+ </div>
151
+ <div class="absolute top-0 right-0 p-2 bg-base-500" v-else-if="presetSortedColumns[column.key] === 'desc'">
152
+ <svg width="19" height="19" viewBox="0 0 24 24" class="fill-text-400" xmlns="http://www.w3.org/2000/svg">
153
+ <path d="M6.75 5C6.75 4.58579 6.41421 4.25 6 4.25C5.58579 4.25 5.25 4.58579 5.25 5V17.6893L3.53033 15.9697C3.23744 15.6768 2.76256 15.6768 2.46967 15.9697C2.17678 16.2626 2.17678 16.7374 2.46967 17.0303L4.76256 19.3232C5.44598 20.0066 6.55402 20.0066 7.23744 19.3232L9.53033 17.0303C9.82322 16.7374 9.82322 16.2626 9.53033 15.9697C9.23744 15.6768 8.76256 15.6768 8.46967 15.9697L6.75 17.6893V5Z"/>
154
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 7C12.25 6.58579 12.5858 6.25 13 6.25H21C21.4142 6.25 21.75 6.58579 21.75 7C21.75 7.41421 21.4142 7.75 21 7.75H13C12.5858 7.75 12.25 7.41421 12.25 7Z"/>
155
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 12C12.25 12.4142 12.5858 12.75 13 12.75H18C18.4142 12.75 18.75 12.4142 18.75 12C18.75 11.5858 18.4142 11.25 18 11.25H13C12.5858 11.25 12.25 11.5858 12.25 12Z"/>
156
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 17C12.25 17.4142 12.5858 17.75 13 17.75H15C15.4142 17.75 15.75 17.4142 15.75 17C15.75 16.5858 15.4142 16.25 15 16.25H13C12.5858 16.25 12.25 16.5858 12.25 17Z"/>
157
+ </svg>
158
+ </div>
109
159
  </div>
110
- <div class="absolute top-0 right-0 p-2 bg-base-500" v-else-if="presetSortedColumns[column.key] === 'desc'">
111
- <svg width="19" height="19" viewBox="0 0 24 24" class="fill-text-400" xmlns="http://www.w3.org/2000/svg">
112
- <path d="M6.75 5C6.75 4.58579 6.41421 4.25 6 4.25C5.58579 4.25 5.25 4.58579 5.25 5V17.6893L3.53033 15.9697C3.23744 15.6768 2.76256 15.6768 2.46967 15.9697C2.17678 16.2626 2.17678 16.7374 2.46967 17.0303L4.76256 19.3232C5.44598 20.0066 6.55402 20.0066 7.23744 19.3232L9.53033 17.0303C9.82322 16.7374 9.82322 16.2626 9.53033 15.9697C9.23744 15.6768 8.76256 15.6768 8.46967 15.9697L6.75 17.6893V5Z"/>
113
- <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 7C12.25 6.58579 12.5858 6.25 13 6.25H21C21.4142 6.25 21.75 6.58579 21.75 7C21.75 7.41421 21.4142 7.75 21 7.75H13C12.5858 7.75 12.25 7.41421 12.25 7Z"/>
114
- <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 12C12.25 12.4142 12.5858 12.75 13 12.75H18C18.4142 12.75 18.75 12.4142 18.75 12C18.75 11.5858 18.4142 11.25 18 11.25H13C12.5858 11.25 12.25 11.5858 12.25 12Z"/>
115
- <path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 17C12.25 17.4142 12.5858 17.75 13 17.75H15C15.4142 17.75 15.75 17.4142 15.75 17C15.75 16.5858 15.4142 16.25 15 16.25H13C12.5858 16.25 12.25 16.5858 12.25 17Z"/>
116
- </svg>
160
+ </template>
161
+ <template v-for="(_, slot) in headerSlots" #[slot]="{ item, index }">
162
+ <div :class="getHeader(slot.replace('col-', ''))">
163
+ <slot :name="slot" :item="item" :index="index"></slot>
117
164
  </div>
118
- </div>
119
- </template>
120
- <template v-for="(_, slot) in headerSlots" #[slot]="{ item, index }">
121
- <div :class="getHeader(slot.replace('col-', ''))">
165
+ </template>
166
+ <template v-for="(_, slot) in contentSlots" #[slot]="{ item, index }">
122
167
  <slot :name="slot" :item="item" :index="index"></slot>
123
- </div>
124
- </template>
125
- <template v-for="(_, slot) in contentSlots" #[slot]="{ item, index }">
126
- <slot :name="slot" :item="item" :index="index"></slot>
127
- </template>
128
- </VirtualTable>
129
- </div>
168
+ </template>
169
+ </VirtualTable>
170
+ </div>
130
171
 
131
- <ContextMenu ref="columnMenu" :dismiss="false">
132
- <div class="flex-1 flex flex-col w-[270px] p-3">
133
- <div class="flex flex-col">
134
- <div class="flex flex-row gap-2 items-center">
135
- <div class="p-2 text-text-300 flex-1">Sort By</div>
136
- <div class="text-primary cursor-pointer text-sm" @click="openPreset('sort');$refs.columnMenu.close()">Sort Options</div>
137
- </div>
138
- <div class="p-2 cursor-pointer" :class="$style.hoverable" @click="setSortCurrent(1)">Sort Ascending</div>
139
- <div class="p-2 cursor-pointer" :class="$style.hoverable" @click="setSortCurrent(2)">Sort Descending</div>
140
- </div>
141
- <div class="h-[1px] bg-text-50 my-2"></div>
142
- <div class="flex flex-col">
143
- <div class="p-2 cursor-pointer" :class="$style.hoverable" @click="hide">Hide</div>
144
- <div class="p-2 cursor-pointer" :class="$style.hoverable" @click="openPreset();$refs.columnMenu.close()">Column Options</div>
145
- </div>
146
- <div class="h-[1px] bg-text-50 my-2"></div>
147
- <div class="flex flex-col">
148
- <div class="flex flex-row gap-2 items-center">
149
- <div class="p-2 text-text-300 flex-1">Filters</div>
150
- <div class="text-primary cursor-pointer text-sm" @click="openPreset('filter');$refs.columnMenu.close()">Filter Options</div>
172
+ <ContextMenu ref="columnMenu" :dismiss="false">
173
+ <div class="flex-1 flex flex-col w-[270px] p-3">
174
+ <div class="flex flex-col">
175
+ <div class="flex flex-row gap-2 items-center">
176
+ <div class="p-2 text-text-300 flex-1">Sort By</div>
177
+ <div class="text-primary cursor-pointer text-sm" @click="openPreset('sort');$refs.columnMenu.close()">Sort Options</div>
178
+ </div>
179
+ <div class="p-2 cursor-pointer" :class="$style.hoverable" @click="setSortCurrent(1)">Sort Ascending</div>
180
+ <div class="p-2 cursor-pointer" :class="$style.hoverable" @click="setSortCurrent(2)">Sort Descending</div>
151
181
  </div>
152
- <div v-if="presetCurrentFilters.length > 0">
153
- <ListPage1Filter v-if="preset.filters" v-for="filter in presetCurrentFilters"
154
- :filter="filter" :column="config.columns[config.columns.findIndex((_) => _.key === filter.key)]"
155
- @remove="removeFilter(filter)" @change="load" />
182
+ <div class="h-[1px] bg-text-50 my-2"></div>
183
+ <div class="flex flex-col">
184
+ <div class="p-2 cursor-pointer" :class="$style.hoverable" @click="hide">Hide</div>
185
+ <div class="p-2 cursor-pointer" :class="$style.hoverable" @click="openPreset();$refs.columnMenu.close()">Column Options</div>
156
186
  </div>
157
- <div v-else>
158
- <div class="p-2 cursor-pointer" :class="$style.hoverable" @click.stop="addCurrentFilter">Add Filter</div>
187
+ <div class="h-[1px] bg-text-50 my-2"></div>
188
+ <div class="flex flex-col">
189
+ <div class="flex flex-row gap-2 items-center">
190
+ <div class="p-2 text-text-300 flex-1">Filters</div>
191
+ <div class="text-primary cursor-pointer text-sm" @click="openPreset('filter');$refs.columnMenu.close()">Filter Options</div>
192
+ </div>
193
+ <div v-if="presetCurrentFilters.length > 0">
194
+ <ListPage1Filter v-if="preset.filters" v-for="filter in presetCurrentFilters"
195
+ :filter="filter" :column="config.columns[config.columns.findIndex((_) => _.key === filter.key)]"
196
+ @remove="removeFilter(filter)" @change="load" />
197
+ </div>
198
+ <div v-else>
199
+ <div class="p-2 cursor-pointer" :class="$style.hoverable" @click.stop="addCurrentFilter">Add Filter</div>
200
+ </div>
159
201
  </div>
160
202
  </div>
161
- </div>
162
- </ContextMenu>
203
+ </ContextMenu>
163
204
 
164
- </div>
205
+ </div>
206
+ </Suspense>
165
207
  </template>
166
208
 
167
209
  <script>
@@ -170,10 +212,9 @@ import throttle from "lodash/throttle";
170
212
  import { Bar, Line } from 'vue-chartjs'
171
213
  import Chart from 'chart.js/auto'
172
214
  import dayjs from "dayjs";
173
- import VirtualTable from "./VirtualTable.vue"
174
215
 
175
216
  export default{
176
- components: {Line, Bar, VirtualTable},
217
+ components: {Line, Bar},
177
218
 
178
219
  emits: [ ],
179
220
 
@@ -192,7 +233,11 @@ export default{
192
233
  configStore: String,
193
234
  subscription: String,
194
235
 
195
- title: String
236
+ title: String,
237
+
238
+ optBar: String,
239
+
240
+ containerClass: String
196
241
 
197
242
  },
198
243
 
@@ -256,6 +301,10 @@ export default{
256
301
  this.mediaPrefix = prefix
257
302
  },
258
303
 
304
+ changeViewType(type){
305
+ this.config.viewType = type
306
+ },
307
+
259
308
  clearSearch(){
260
309
  this.preset.search = ''
261
310
  this.load()
@@ -284,20 +333,27 @@ export default{
284
333
  async load(){
285
334
  if(!this.src) return
286
335
 
287
- this.$refs.table1 ? this.$refs.table1.setState(3) :
288
- (this.$refs.table2 ? this.$refs.table2.setState(3) : null)
289
- this.socketEmit2(this.src, { columns:this.config.columns, preset:this.preset })
336
+ this.$refs.table1 ? this.$refs.table1.setState(3) : null
337
+
338
+ let itemsPerPage = 16
339
+ switch(this.computedViewType){
340
+ case 'grid':
341
+ itemsPerPage *= this.gridColumn
342
+ break
343
+ case 'table':
344
+ itemsPerPage = 24
345
+ break
346
+ }
347
+
348
+ this.socketEmit2(this.src, { columns:this.config.columns, preset:this.preset, itemsPerPage })
290
349
  .then((res) => {
291
350
  Object.assign(this.$data, res)
292
351
  })
293
- .finally(() => {
294
- this.$refs.table1 ? this.$refs.table1.setState(1) :
295
- (this.$refs.table2 ? this.$refs.table2.setState(1) : null)
296
- })
352
+ .finally(() => this.$refs.table1 ? this.$refs.table1.resetState() : null)
297
353
  },
298
354
 
299
355
  loadNext(){
300
- this.$refs.table1 ? this.$refs.table1.setState(2) : this.$refs.table2.setState(2)
356
+ this.$refs.table1 ? this.$refs.table1.setState(2) : null
301
357
  this.socketEmit2(this.src, {
302
358
  preset: this.preset,
303
359
  afterItem: this.items[this.items.length - 1]
@@ -309,33 +365,27 @@ export default{
309
365
  .catch((err) => {
310
366
  this.toast(err)
311
367
  })
312
- .finally(() => {
313
- this.$refs.table1 ? this.$refs.table1.setState(1) : this.$refs.table2.setState(1)
314
- })
368
+ .finally(() => this.$refs.table1 ? this.$refs.table1.resetState() : null)
315
369
  },
316
370
 
317
371
  async loadConfig(){
318
-
319
372
  if(!this.configStoreObj || 'reset' in ((this.$route ?? {}).query ?? {})){
320
373
  this.configLoaded = true
321
-
322
374
  if('reset' in ((this.$route ?? {}).query ?? {}))
323
375
  await this.saveConfig()
324
-
325
- return
326
376
  }
327
-
328
- switch(this.configStoreObj.type){
329
- case 'socket':
330
- return this.socketEmit2(this.configStoreObj.src,
377
+ else{
378
+ switch(this.configStoreObj.type){
379
+ case 'socket':
380
+ return this.socketEmit2(this.configStoreObj.src,
331
381
  { key:this.configStoreObj.name ?? 'ListView' })
332
- .then((config) => {
333
- if(config && Array.isArray(config.columns)){
334
- Object.assign(this.config, config ?? {})
335
- }
336
-
337
- this.configLoaded = true
338
- })
382
+ .then((config) => {
383
+ if(config && Array.isArray(config.columns)){
384
+ Object.assign(this.config, config ?? {})
385
+ }
386
+ this.configLoaded = true
387
+ })
388
+ }
339
389
  }
340
390
  },
341
391
 
@@ -533,6 +583,17 @@ export default{
533
583
 
534
584
  computed: {
535
585
 
586
+ computedViewType(){
587
+ if(!this.config.viewType){
588
+ this.config.viewType = 'table'
589
+ }
590
+
591
+ if(this.$screenPrefix.value === ''){
592
+ return 'grid'
593
+ }
594
+
595
+ return this.config.viewType
596
+ },
536
597
 
537
598
  chartOptions(){
538
599
 
@@ -655,6 +716,30 @@ export default{
655
716
  }*/
656
717
  },
657
718
 
719
+ gridColumn(){
720
+ if(!this.config.gridColumn){
721
+ this.config.gridColumn = 3
722
+ }
723
+
724
+ if(this.$screenPrefix.value === ''){
725
+ return 1
726
+ }
727
+
728
+ return this.config.gridColumn
729
+ },
730
+
731
+ gridGap(){
732
+ if(!this.config.gridGap){
733
+ this.config.gridGap = 'gap-3'
734
+ }
735
+
736
+ if(this.$screenPrefix.value === ''){
737
+ return 'gap-1'
738
+ }
739
+
740
+ return this.config.gridGap
741
+ },
742
+
658
743
  subscriptionObj(){
659
744
  const splitted = ((this.subscription ?? '').toString()).split(':')
660
745
  const splitted2 = (splitted[1] ?? '').split(',')
@@ -835,6 +920,10 @@ export default{
835
920
  @apply flex-1 flex flex-col md:gap-4;
836
921
  }
837
922
 
923
+ .toolbar{
924
+ @apply flex flex-row items-center gap-4 px-6 md:px-0 py-4 md:py-0 bg-base-400 dark:bg-base-300 md:bg-transparent;
925
+ }
926
+
838
927
  .searchBox{
839
928
  @apply !bg-base-400 md:w-[300px];
840
929
  }
@@ -858,4 +947,8 @@ export default{
858
947
  @apply hover:bg-primary hover:text-white;
859
948
  }
860
949
 
950
+ .mobileItem{
951
+ @apply p-3 border-[1px] border-text-100 bg-base-300 rounded-lg;
952
+ }
953
+
861
954
  </style>
@@ -89,7 +89,7 @@ export default{
89
89
  @apply border-[1px] border-primary rounded-xl;
90
90
  }
91
91
  .button>*{
92
- @apply p-[1px] px-2 m-[1px];
92
+ @apply p-[1px] px-2;
93
93
  }
94
94
  .button>*:first-child{
95
95
  @apply rounded-l-lg;
@@ -101,7 +101,7 @@ export default{
101
101
  @apply bg-primary text-text-500;
102
102
  }
103
103
  .button>*[class*="active"] *{
104
- @apply text-white;
104
+ @apply text-white fill-white;
105
105
  }
106
106
 
107
107
  </style>
@@ -0,0 +1,269 @@
1
+ <template>
2
+ <div :class="$style.virtualGrid" @click="resize">
3
+
4
+ <div ref="scroller" :class="$style.scroller" :style="scrollerStyle">
5
+ <div :class="spacerClass" ref="spacer" :style="spacerStyle">
6
+ <div v-for="(item, index) in visibleItems" :key="item" :data-id="item.id"
7
+ @click="">
8
+ <slot name="item" :item="item" :index="index"></slot>
9
+ </div>
10
+ </div>
11
+ </div>
12
+
13
+ <div :class="$style.calc" v-if="items && items.length > 0" ref="calc">
14
+ <slot name="item" :item="items[0]" :index="0"></slot>
15
+ </div>
16
+
17
+ <Teleport v-if="optBar" :to="optBar">
18
+ <div class="flex flex-row gap-2 items-center">
19
+ <small class="text-text-400">Column</small>
20
+ <select v-model="config.gridColumn"
21
+ class="appearance-none outline-none text-text-400 w-[20px] bg-transparent border-[1px] border-text-100 text-center">
22
+ <option v-for="i in 8" :value="i">{{ i }}</option>
23
+ </select>
24
+ </div>
25
+ <div class="flex flex-row gap-2 items-center">
26
+ <small class="text-text-400">Gap</small>
27
+ <select v-model="config.gridGap"
28
+ class="appearance-none outline-none text-text-400 w-[20px] bg-transparent border-[1px] border-text-100 text-center">
29
+ <option v-for="i in 8" :value="`gap-${i}`">{{ i }}</option>
30
+ </select>
31
+ </div>
32
+ </Teleport>
33
+
34
+ </div>
35
+ </template>
36
+
37
+ <script>
38
+
39
+ import throttle from "lodash/throttle";
40
+
41
+ export default{
42
+
43
+ emits: [ 'scroll-end' ],
44
+
45
+ props: {
46
+
47
+ column: {
48
+ type: [ Number, String ],
49
+ default: 1
50
+ },
51
+
52
+ gap: {
53
+ type: [ String ],
54
+ default: 'gap-6'
55
+ },
56
+
57
+ items: Array,
58
+
59
+ pinned: Function,
60
+
61
+ optBar: String,
62
+
63
+ config: Object
64
+
65
+ },
66
+
67
+ methods: {
68
+
69
+ handleScroll: throttle(function(){
70
+ this.scrollTop = this.$el.scrollTop
71
+
72
+ if(this.scrollTop > this.$refs.scroller.offsetHeight - this.$el.clientHeight - this.itemHeight){
73
+ if(!this.isOnEndScroll){
74
+ this.$emit('scroll-end')
75
+ this.isOnEndScroll = true
76
+ }
77
+ }
78
+ else{
79
+ if(this.isOnEndScroll){
80
+ this.isOnEndScroll = false
81
+ }
82
+ }
83
+ }, 16),
84
+
85
+ init(){
86
+ this.$el.addEventListener(
87
+ "scroll",
88
+ this.handleScroll,
89
+ this.passiveScrollSupported() ? { passive: true } : false
90
+ )
91
+
92
+ this.resize()
93
+ },
94
+
95
+ passiveScrollSupported() {
96
+ let passiveSupported = false;
97
+
98
+ try {
99
+ const options = {
100
+ get passive() {
101
+ passiveSupported = true;
102
+ return false;
103
+ }
104
+ };
105
+ window.addEventListener("test", null, options);
106
+ window.removeEventListener("test", null, options);
107
+ } catch (err) {
108
+ passiveSupported = false;
109
+ }
110
+ return passiveSupported;
111
+ },
112
+
113
+ resetState(){
114
+ this.state = 1
115
+ },
116
+
117
+ async resize(){
118
+
119
+ this.$nextTick(() => {
120
+ if(this.$refs.calc){
121
+ const elHeight = parseInt(window.getComputedStyle(this.$el).height !== '0px' ?
122
+ window.getComputedStyle(this.$el).height :
123
+ window.getComputedStyle(this.$el).maxHeight)
124
+
125
+ if(isNaN(elHeight)) return
126
+
127
+ this.itemHeight = parseFloat(window.getComputedStyle(this.$refs.calc).height.replace('px', ''))
128
+
129
+ this.itemGap = parseFloat(window.getComputedStyle(this.$refs.spacer)['row-gap'].replace('px', ''));
130
+
131
+ this.maxVisibleItems = elHeight > 0 ? (Math.ceil(elHeight / this.itemHeight) * this.column) : this.items.length
132
+
133
+ if(this.itemHeight <= 0){
134
+ console.error('Unable to calculate virtual grid item height, async component not supported.')
135
+ }
136
+ }
137
+ })
138
+
139
+ },
140
+
141
+ setState(state){
142
+ this.state = state
143
+ }
144
+
145
+ },
146
+
147
+ computed: {
148
+
149
+ scrollerStyle(){
150
+ if(!this.items || this.items.length < 1)
151
+ return {}
152
+
153
+ const rowCount = Math.ceil((this.items.length / this.column))
154
+ const height = (rowCount * this.itemHeight) + (this.itemGap * (rowCount - 1))
155
+
156
+ return {
157
+ height: height + 'px'
158
+ }
159
+ },
160
+
161
+ sortedItems(){
162
+ if(!Array.isArray(this.items)) return []
163
+
164
+ if(typeof this.pinned === 'function'){
165
+ const pinnedItems = []
166
+ const unpinnedItems = []
167
+ this.items.forEach((item) => {
168
+ if(this.pinned(item))
169
+ pinnedItems.push(item)
170
+ else
171
+ unpinnedItems.push(item)
172
+ })
173
+
174
+ return [
175
+ ...pinnedItems,
176
+ ...unpinnedItems
177
+ ]
178
+ }
179
+ return this.items
180
+ },
181
+
182
+ spacerClass(){
183
+ return [
184
+ this.$style.spacer,
185
+ this.gap
186
+ ]
187
+ .join(' ')
188
+ },
189
+
190
+ spacerStyle(){
191
+ return {
192
+ transform: "translateY(" + (this.visibleStartIndex * (this.itemHeight + this.itemGap)) + "px)",
193
+ 'grid-template-columns': `repeat(${this.column}, minmax(0, 1fr))`,
194
+ gap: this.gap
195
+ }
196
+ },
197
+
198
+ visibleItems(){
199
+ if(this.itemHeight <= 0) return []
200
+ return this.sortedItems.slice(this.visibleStartIndex * this.column, (this.visibleStartIndex * this.column) + this.maxVisibleItems)
201
+ },
202
+
203
+ visibleStartIndex(){
204
+ return Math.floor((this.scrollTop < 0 ? 0 : this.scrollTop) / (this.itemHeight + this.itemGap))
205
+ },
206
+
207
+ },
208
+
209
+ data(){
210
+ return {
211
+ scrollTop: 0,
212
+ itemHeight: 0,
213
+ itemGap: 0,
214
+ maxVisibleItems: 0,
215
+ isOnEndScroll: false,
216
+ selectedIndex: -1,
217
+ state: 1,
218
+ }
219
+ },
220
+
221
+ mounted() {
222
+ this.init()
223
+ },
224
+
225
+ watch: {
226
+
227
+ column(to){
228
+ this.resize()
229
+ },
230
+
231
+ gap(to){
232
+ this.resize()
233
+ },
234
+
235
+ items(to){
236
+ this.resize()
237
+ },
238
+
239
+ }
240
+
241
+ }
242
+
243
+ </script>
244
+
245
+ <style module>
246
+
247
+ .virtualGrid{
248
+ @apply flex-1 overflow-y-auto;
249
+ }
250
+
251
+ .scroller{
252
+ position: relative;
253
+ overflow: hidden;
254
+ will-change: auto;
255
+ @apply min-w-full;
256
+ }
257
+
258
+ .spacer{
259
+ will-change: auto;
260
+ position: relative;
261
+ @apply grid;
262
+ }
263
+
264
+ .calc{
265
+ @apply absolute invisible;
266
+ top: -10000px;
267
+ }
268
+
269
+ </style>
@@ -43,6 +43,11 @@ export default{
43
43
 
44
44
  pinned: Function,
45
45
 
46
+ gap: {
47
+ type: [ String ],
48
+ default: '0'
49
+ },
50
+
46
51
  },
47
52
 
48
53
  computed:{
@@ -79,7 +84,8 @@ export default{
79
84
 
80
85
  spacerStyle(){
81
86
  return {
82
- transform: "translateY(" + (this.visibleStartIndex * this.itemHeight) + "px)"
87
+ transform: "translateY(" + (this.visibleStartIndex * this.itemHeight) + "px)",
88
+ gap: this.gap
83
89
  }
84
90
  },
85
91
 
@@ -87,8 +93,10 @@ export default{
87
93
  if(!this.items || this.items.length < 1)
88
94
  return {}
89
95
 
90
- const height = (this.items.length * this.itemHeight)
91
- //console.log('scrollerStyle', height)
96
+ let gapPx = parseInt(this.gap)
97
+ if(isNaN(gapPx)) gapPx = 0
98
+
99
+ const height = (this.items.length * this.itemHeight) + (gapPx * (this.items.length - 1))
92
100
 
93
101
  return {
94
102
  height: height + 'px'
@@ -124,6 +132,10 @@ export default{
124
132
  this.resize()
125
133
  },
126
134
 
135
+ resetState(){
136
+ this.state = 1
137
+ },
138
+
127
139
  select(item, index){
128
140
  this.selectedIndex = (item && item.id) ? item.id : this.visibleStartIndex + index
129
141
  if(item._highlight){
@@ -145,7 +157,7 @@ export default{
145
157
  this.isOnEndScroll = false
146
158
  }
147
159
  }
148
- }, 35),
160
+ }, 16),
149
161
 
150
162
  passiveScrollSupported() {
151
163
  let passiveSupported = false;
@@ -175,11 +187,9 @@ export default{
175
187
 
176
188
  if(isNaN(elHeight)) return
177
189
 
178
- this.itemHeight = parseInt(window.getComputedStyle(this.$refs.calc).height)
190
+ this.itemHeight = parseFloat(window.getComputedStyle(this.$refs.calc).height)
179
191
  this.maxVisibleItems = elHeight > 0 ? Math.ceil(elHeight / this.itemHeight) + 1 : this.items.length
180
192
 
181
- //console.log('Virtual scroll resize', { elHeight, itemHeight:this.itemHeight, maxVisibleItems:this.maxVisibleItems })
182
-
183
193
  if(this.itemHeight <= 0){
184
194
  console.error('[VirtualScroll] Unable to calculate item height, make sure not async component.')
185
195
  }
@@ -219,12 +229,13 @@ export default{
219
229
  position: relative;
220
230
  overflow: hidden;
221
231
  will-change: auto;
222
- @apply min-w-full
232
+ @apply min-w-full;
223
233
  }
224
234
 
225
235
  .spacer{
226
236
  will-change: auto;
227
237
  position: relative;
238
+ @apply flex flex-col;
228
239
  }
229
240
 
230
241
  .item:hover{
@@ -197,6 +197,10 @@ export default{
197
197
 
198
198
  methods: {
199
199
 
200
+ resetState(){
201
+ this.state = 1
202
+ },
203
+
200
204
  trClass(item, index){
201
205
  const highlight = !!item._highlight && (!Array.isArray(item._highlight) ||
202
206
  this.visibleColumns.filter((_) => item._highlight.includes(_.key)).length <= 0)
@@ -237,8 +241,6 @@ export default{
237
241
  this.itemHeight = parseInt(window.getComputedStyle(this.$refs.calc).height)
238
242
  this.maxVisibleItems = elHeight > 0 ? Math.ceil(elHeight / this.itemHeight) + 1 : this.items.length
239
243
 
240
- //console.log('Virtual table scroll resize', { elHeight, itemHeight:this.itemHeight, maxVisibleItems:this.maxVisibleItems })
241
-
242
244
  if(this.itemHeight <= 0){
243
245
  console.error('[VirtualTable] Unable to calculate item height, make sure not async component.')
244
246
  }
@@ -262,7 +264,7 @@ export default{
262
264
  this.isOnEndScroll = false
263
265
  }
264
266
  }
265
- }, 35),
267
+ }, 16),
266
268
 
267
269
  passiveScrollSupported() {
268
270
  let passiveSupported = false;
package/src/index.js CHANGED
@@ -282,6 +282,45 @@ export default{
282
282
  window.addEventListener('resize', onWindowResize)
283
283
  }
284
284
 
285
+ const tooltips = {}
286
+
287
+ app.directive('tooltip', (el, binding) => {
288
+ el.addEventListener('mouseover', () => {
289
+
290
+ if(!el.getAttribute('data-tooltip')){
291
+ const tooltip = document.createElement('div')
292
+ tooltip.classList.add('tooltip')
293
+ tooltip.innerHTML = binding.value
294
+ document.body.appendChild(tooltip)
295
+
296
+ const uid = uniqid()
297
+ el.setAttribute('data-tooltip', uid)
298
+ tooltips[uid] = tooltip
299
+ }
300
+
301
+ window.setTimeout(() => {
302
+ if(el.getAttribute('data-tooltip-open')){
303
+ const uid = el.getAttribute('data-tooltip')
304
+ const tooltip = tooltips[uid]
305
+ const rect = el.getBoundingClientRect()
306
+ tooltip.style.top = (rect.y + rect.height + 10) + 'px'
307
+ tooltip.style.left = rect.x - (Math.round((tooltip.clientWidth - rect.width) / 2)) + 'px'
308
+ tooltip.classList.add('active')
309
+ }
310
+ }, 1300)
311
+ el.setAttribute('data-tooltip-open', true)
312
+ })
313
+
314
+ el.addEventListener('mouseout', () => {
315
+ el.removeAttribute('data-tooltip-open')
316
+ const uid = el.getAttribute('data-tooltip')
317
+ const tooltip = tooltips[uid]
318
+ if(tooltip){
319
+ tooltip.classList.remove('active')
320
+ }
321
+ })
322
+ })
323
+
285
324
  app.config.globalProperties.uniqid = uniqid
286
325
  app.config.globalProperties.$download = download
287
326
  app.config.globalProperties.$preload = preload
@@ -350,6 +389,7 @@ export default{
350
389
  app.component('Toast', defineAsyncComponent(() => import("./components/Toast.vue")))
351
390
  app.component('TreeView', defineAsyncComponent(() => import("./components/TreeView.vue")))
352
391
  app.component('TreeViewItem', defineAsyncComponent(() => import("./components/TreeViewItem.vue")))
392
+ app.component('VirtualGrid', defineAsyncComponent(() => import("./components/VirtualGrid.vue")))
353
393
  app.component('VirtualScroll', defineAsyncComponent(() => import("./components/VirtualScroll.vue")))
354
394
  app.component('VirtualTable', defineAsyncComponent(() => import("./components/VirtualTable.vue")))
355
395
  app.component('Slider', defineAsyncComponent(() => import("./components/Slider.vue")))
@@ -100,7 +100,7 @@ const plugin = Plugin(function({ addBase, config, theme }) {
100
100
  'fontSize': '14px',
101
101
  },
102
102
 
103
- "input, input[type='text'], input[type='number'], textarea, select, option": { fontSize:"16px" }
103
+ "input, input[type='text'], input[type='number'], textarea, select, option": { fontSize:"16px !important" }
104
104
  },
105
105
 
106
106
  '::-webkit-scrollbar': {
@@ -143,7 +143,22 @@ const plugin = Plugin(function({ addBase, config, theme }) {
143
143
  fontWeight: theme('fontWeight.bold'),
144
144
  },
145
145
 
146
- '.flex-1': { minHeight: "0px", minWidth: "0px" }
146
+ '.flex-1': { minHeight: "0px", minWidth: "0px" },
147
+
148
+ '.tooltip': {
149
+ position: "fixed",
150
+ 'z-index': 1000,
151
+ transform: 'scale(0)',
152
+ padding: '.3rem .6rem',
153
+ background: 'rgb(var(--text-50))',
154
+ 'border-radius': '5px',
155
+ transition: 'transform .2s ease-in-out',
156
+ 'transform-origin': 'top',
157
+ },
158
+
159
+ '.tooltip.active': {
160
+ transform: 'scale(1)',
161
+ }
147
162
 
148
163
  })
149
164
 
@@ -365,10 +365,6 @@ let ListView = {
365
365
  }
366
366
  }
367
367
 
368
- if(this.socket){
369
- this.socket.join(this.channel)
370
- }
371
-
372
368
  return {
373
369
  items,
374
370
  hasNext,
@@ -34,7 +34,9 @@ export default{
34
34
 
35
35
  item: Object,
36
36
 
37
- viewTypes: Array
37
+ viewTypes: Array,
38
+
39
+ viewType: String
38
40
 
39
41
  }
40
42
 
@@ -6,7 +6,7 @@
6
6
  <p v-if="description" class="mt-2 text-lg">{{ description }}</p>
7
7
  <div :class="computedContainerClass">
8
8
  <div v-for="item in items" class="flex gap-4" :class="computedItemClass">
9
- <div :class="imageClass">
9
+ <div>
10
10
  <Image :src="imageUrl(item.imageUrl)" class="bg-gray-200 rounded-xl w-full aspect-square object-contain" />
11
11
  </div>
12
12
  <div class="flex-1 flex flex-col gap-1">
@@ -68,16 +68,6 @@ export default{
68
68
  .join(' ')
69
69
  },
70
70
 
71
- imageClass(){
72
-
73
- return [
74
- this.variant && this.variant[0] === 'variant2' ? 'w-[72px]' : 'w-[72px]',
75
- this.variant && this.variant[0] === 'variant2' ? 'md:w-[100px]' : 'md:w-[100px]'
76
- ]
77
- .filter(_ => _)
78
- .join(' ')
79
- }
80
-
81
71
  }
82
72
 
83
73
  }
@@ -115,15 +105,23 @@ export default{
115
105
 
116
106
  .fl-uO88{
117
107
  }
118
-
119
- .fl-uO89{
108
+ .fl-uO88 img{
109
+ @apply w-[80px];
110
+ }
111
+ .fl-uO89 img{
112
+ @apply w-[160px];
120
113
  }
121
114
 
122
115
  @media screen(md){
123
- .md\:fl-uO88{
116
+ .md\:fl-uO88 img{
117
+ @apply w-[120px];
124
118
  }
125
119
 
126
120
  .md\:fl-uO89{
121
+ @apply text-center;
122
+ }
123
+ .md\:fl-uO89 img{
124
+ @apply w-[160px];
127
125
  }
128
126
  }
129
127
 
@@ -7,7 +7,19 @@
7
7
  <Textarea v-model="item.props.description" rows="3" @blur="$emit('change')" placeholder="Description" />
8
8
  </div>
9
9
 
10
- <div>
10
+ <div class="flex flex-col gap-1">
11
+ <label class="text-text-400">Mode</label>
12
+ <Dropdown v-model="item.props.mode" @change="item.props.items = []">
13
+ <option value="">Static</option>
14
+ <option value="dynamic">Dynamic</option>
15
+ </Dropdown>
16
+ <Textbox v-if="item.props.mode === 'dynamic'"
17
+ placeholder="Var" v-model="item.props.src"
18
+ @blur="$emit('change')"
19
+ @keyup.enter="$emit('change')" />
20
+ </div>
21
+
22
+ <div v-if="item.props.mode !== 'dynamic'">
11
23
  <div class="flex flex-row gap-4">
12
24
  <label class="flex-1 text-text-400">Items</label>
13
25
  <button type="button" class="text-primary"
@@ -37,6 +49,21 @@
37
49
  </ListItem>
38
50
  </div>
39
51
 
52
+ <div v-else>
53
+ <label class="flex-1 text-text-400">Item</label>
54
+ <div class="mt-1 flex flex-col gap-1">
55
+ <Textbox v-model="itemPropsItem.title" placeholder="Title var"
56
+ @blur="$emit('change')"
57
+ @keyup.enter="$emit('change')"/>
58
+ <Textbox v-model="itemPropsItem.description" placeholder="Description var"
59
+ @blur="$emit('change')"
60
+ @keyup.enter="$emit('change')"/>
61
+ <Textbox v-model="itemPropsItem.imageUrl" placeholder="Image var"
62
+ @blur="$emit('change')"
63
+ @keyup.enter="$emit('change')"/>
64
+ </div>
65
+ </div>
66
+
40
67
  <div class="flex flex-row items-center gap-4">
41
68
  <label class="flex-1 text-text-400">Columns</label>
42
69
  <Dropdown v-for="(_viewType, idx) in viewTypes" class="w-[60px]"
@@ -154,6 +181,12 @@ export default{
154
181
  return this.item.props.itemVariant
155
182
  },
156
183
 
184
+ itemPropsItem(){
185
+ if(!this.item.props.item)
186
+ this.item.props.item = {}
187
+ return this.item.props.item
188
+ }
189
+
157
190
  }
158
191
 
159
192
  }
@@ -14,7 +14,19 @@
14
14
  </div>
15
15
 
16
16
  <div class="flex flex-col items-center pb-6">
17
- <Tabs variant="button" :items="tabItems" v-model="store.tabIndex" />
17
+ <Tabs variant="button" :items="tabItems" v-model="store.tabIndex">
18
+ <template #tab="{ item }">
19
+ <div v-if="item.value === 1" v-tooltip="'Page Info'" class="p-1 px-2">
20
+ <svg width="14" height="14" class="fill-text pointer-events-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 40c118.621 0 216 96.075 216 216 0 119.291-96.61 216-216 216-119.244 0-216-96.562-216-216 0-119.203 96.602-216 216-216m0-32C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm-36 344h12V232h-12c-6.627 0-12-5.373-12-12v-8c0-6.627 5.373-12 12-12h48c6.627 0 12 5.373 12 12v140h12c6.627 0 12 5.373 12 12v8c0 6.627-5.373 12-12 12h-72c-6.627 0-12-5.373-12-12v-8c0-6.627 5.373-12 12-12zm36-240c-17.673 0-32 14.327-32 32s14.327 32 32 32 32-14.327 32-32-14.327-32-32-32z"/></svg>
21
+ </div>
22
+ <div v-else-if="item.value === 2" v-tooltip="'Components'" class="p-1 px-2">
23
+ <svg width="14" height="14" class="fill-text pointer-events-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M512 256.01c0-9.98-5.81-18.94-14.77-22.81l-99.74-43.27 99.7-43.26c9-3.89 14.81-12.84 14.81-22.81s-5.81-18.92-14.77-22.79L271.94 3.33c-10.1-4.44-21.71-4.45-31.87-.02L14.81 101.06C5.81 104.95 0 113.9 0 123.87s5.81 18.92 14.77 22.79l99.73 43.28-99.7 43.26C5.81 237.08 0 246.03 0 256.01c0 9.97 5.81 18.92 14.77 22.79l99.72 43.26-99.69 43.25C5.81 369.21 0 378.16 0 388.14c0 9.97 5.81 18.92 14.77 22.79l225.32 97.76a40.066 40.066 0 0 0 15.9 3.31c5.42 0 10.84-1.1 15.9-3.31l225.29-97.74c9-3.89 14.81-12.84 14.81-22.81 0-9.98-5.81-18.94-14.77-22.81l-99.72-43.26 99.69-43.25c9-3.89 14.81-12.84 14.81-22.81zM45.23 123.87l208.03-90.26.03-.02c1.74-.71 3.65-.76 5.45.02l208.03 90.26-208.03 90.27c-1.81.77-3.74.77-5.48 0L45.23 123.87zm421.54 264.27L258.74 478.4c-1.81.77-3.74.77-5.48 0L45.23 388.13l110.76-48.06 84.11 36.49a40.066 40.066 0 0 0 15.9 3.31c5.42 0 10.84-1.1 15.9-3.31l84.11-36.49 110.76 48.07zm-208.03-41.87c-1.81.77-3.74.77-5.48 0L45.23 256 156 207.94l84.1 36.5a40.066 40.066 0 0 0 15.9 3.31c5.42 0 10.84-1.1 15.9-3.31l84.1-36.49 110.77 48.07-208.03 90.25z"/></svg>
24
+ </div>
25
+ <div v-else-if="item.value === 3" v-tooltip="'Datasource'" class="p-1 px-2">
26
+ <svg width="14" height="14" class="fill-text pointer-events-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M224 32c106 0 192 28.75 192 64v32c0 35.25-86 64-192 64S32 163.25 32 128V96c0-35.25 86-64 192-64m192 149.5V224c0 35.25-86 64-192 64S32 259.25 32 224v-42.5c41.25 29 116.75 42.5 192 42.5s150.749-13.5 192-42.5m0 96V320c0 35.25-86 64-192 64S32 355.25 32 320v-42.5c41.25 29 116.75 42.5 192 42.5s150.749-13.5 192-42.5m0 96V416c0 35.25-86 64-192 64S32 451.25 32 416v-42.5c41.25 29 116.75 42.5 192 42.5s150.749-13.5 192-42.5M224 0C145.858 0 0 18.801 0 96v320c0 77.338 146.096 96 224 96 78.142 0 224-18.801 224-96V96c0-77.338-146.096-96-224-96z"/></svg>
27
+ </div>
28
+ </template>
29
+ </Tabs>
18
30
  </div>
19
31
 
20
32
  <div v-if="store.tabIndex === 1" class="flex flex-col gap-4 p-6">
@@ -227,6 +239,14 @@
227
239
 
228
240
  </div>
229
241
 
242
+ <div v-else-if="store.tabIndex === 3" class="flex-1">
243
+
244
+ <div class="p-6 text-center">
245
+ <button type="button" class="text-primary">Add Datasource</button>
246
+ </div>
247
+
248
+ </div>
249
+
230
250
  </div>
231
251
 
232
252
  <div :class="$style.resize1"
@@ -266,7 +286,16 @@
266
286
  </template>
267
287
  </Textbox>
268
288
  <div class="px-6">
269
- <Tabs v-model="store.viewType" variant="button" @change="resize" :items="viewTypes" />
289
+ <Tabs v-model="store.viewType" variant="button" @change="resize" :items="viewTypes">
290
+ <template #tab="{ item }">
291
+ <div class="p-1 px-2" v-if="item.value === ''" v-tooltip="'Mobile'">
292
+ <svg width="14" height="14" class="fill-text pointer-events-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M192 416c0 17.7-14.3 32-32 32s-32-14.3-32-32 14.3-32 32-32 32 14.3 32 32zM320 48v416c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V48C0 21.5 21.5 0 48 0h224c26.5 0 48 21.5 48 48zm-32 0c0-8.8-7.2-16-16-16H48c-8.8 0-16 7.2-16 16v416c0 8.8 7.2 16 16 16h224c8.8 0 16-7.2 16-16V48z"/></svg>
293
+ </div>
294
+ <div v-else-if="item.value === 'md:'" v-tooltip="'Tablet'" class="p-1 px-2">
295
+ <svg width="14" height="14" class="fill-text pointer-events-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M400 0H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48zm16 464c0 8.8-7.2 16-16 16H48c-8.8 0-16-7.2-16-16V48c0-8.8 7.2-16 16-16h352c8.8 0 16 7.2 16 16v416zm-140-16H172c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h104c6.6 0 12 5.4 12 12v8c0 6.6-5.4 12-12 12z"/></svg>
296
+ </div>
297
+ </template>
298
+ </Tabs>
270
299
  </div>
271
300
  </div>
272
301
  </div>
@@ -1002,6 +1031,7 @@ export default{
1002
1031
  tabItems: [
1003
1032
  { text:"Page Info", value:1 },
1004
1033
  { text:"Components", value:2 },
1034
+ { text:"Datasource", value:3 },
1005
1035
  ],
1006
1036
  viewTypes: [
1007
1037
  { text:'Mobile', value:'' },