@mixd-id/web-scaffold 0.1.230406041 → 0.1.230406042

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.230406041",
4
+ "version": "0.1.230406042",
5
5
  "scripts": {
6
6
  "dev": "vite serve",
7
7
  "build": "vite build",
@@ -15,12 +15,12 @@
15
15
  <path d="M13 16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16C11 15.4477 11.4477 15 12 15C12.5523 15 13 15.4477 13 16Z"/>
16
16
  <path fill-rule="evenodd" clip-rule="evenodd" d="M12 20.5C16.6944 20.5 20.5 16.6944 20.5 12C20.5 7.30558 16.6944 3.5 12 3.5C7.30558 3.5 3.5 7.30558 3.5 12C3.5 16.6944 7.30558 20.5 12 20.5ZM12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"/>
17
17
  </svg>
18
-
18
+
19
19
  <div :class="$style.cont2">
20
20
  <h5 :class="$style.title">{{ title }}</h5>
21
21
  <p :class="$style.description">{{ description }}</p>
22
22
  </div>
23
-
23
+
24
24
  <div class="mt-4" v-if="mode === 'alert'">
25
25
  <Button @click="$emit('dismiss')" class="min-w-[88px]">
26
26
  <strong class="px-4">
@@ -104,9 +104,8 @@ export default{
104
104
  else if(this.state.title.message){
105
105
  description = this.state.title.message
106
106
  }
107
- else if(this.state.title.reason && this.state.title.reason.message &&
108
- this.state.title.reason.message.message){
109
- description = this.state.title.reason.message.message
107
+ else if(this.state.title.reason && this.state.title.reason.message){
108
+ description = this.state.title.reason.message
110
109
  }
111
110
  }
112
111
  else{
@@ -197,4 +196,4 @@ export default{
197
196
  opacity: 0;
198
197
  }
199
198
 
200
- </style>
199
+ </style>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div :class="$style.comp">
3
+ <h1>{{ title }}</h1>
4
+ </div>
5
+ </template>
6
+
7
+ <script>
8
+
9
+ export default{
10
+
11
+ props: {
12
+ title: String
13
+ }
14
+
15
+ }
16
+
17
+ </script>
18
+
19
+ <style module>
20
+
21
+ .comp{
22
+ @apply flex-1 bg-base-300 flex items-center justify-center;
23
+ }
24
+
25
+ </style>
@@ -0,0 +1,268 @@
1
+ <template>
2
+ <div :class="$style.comp" v-if="true || configLoaded">
3
+
4
+ <div class="flex flex-row items-center gap-4 px-3">
5
+ <div class="flex-1 flex flex-row gap-1">
6
+ <h5>{{ title }}</h5>
7
+ <button type="button">
8
+ <svg width="16" height="16" class="fill-text-300 hover:fill-text" 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>
9
+ </button>
10
+ </div>
11
+ <Textbox placeholder="Cari..." :clearable="true" @clear="clearSearch" v-model="preset.search"
12
+ @keyup.enter="load">
13
+
14
+ </Textbox>
15
+ </div>
16
+
17
+ <VirtualTable ref="table1" :columns="presetColumns" :items="items" class="flex-1"
18
+ @scroll-end="loadNext">
19
+ <template v-for="column in presetColumns" #[colOf(column.key)]="{}">
20
+ <div :class="getHeader(column)">
21
+ <div>
22
+ {{ column.label ?? column.key }}
23
+ </div>
24
+ <div class="absolute top-0 right-0 p-2 bg-base-500" v-if="false">
25
+ <svg width="19" height="19" viewBox="0 0 24 24" class="fill-text-400" xmlns="http://www.w3.org/2000/svg">
26
+ <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"/>
27
+ <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"/>
28
+ <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"/>
29
+ <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"/>
30
+ </svg>
31
+ </div>
32
+ <div class="absolute top-0 right-0 p-2 bg-base-500" v-else-if="false">
33
+ <svg width="19" height="19" viewBox="0 0 24 24" class="fill-text-400" xmlns="http://www.w3.org/2000/svg">
34
+ <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"/>
35
+ <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"/>
36
+ <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"/>
37
+ <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"/>
38
+ </svg>
39
+ </div>
40
+ </div>
41
+ </template>
42
+ <template v-for="(_, slot) in headerSlots" #[slot]="{ item, index }">
43
+ <div :class="getHeader(slot.replace('col-', ''))">
44
+ <slot :name="slot" :item="item" :index="index"></slot>
45
+ </div>
46
+ </template>
47
+ <template v-for="(_, slot) in contentSlots" #[slot]="{ item, index }">
48
+ <slot :name="slot" :item="item" :index="index"></slot>
49
+ </template>
50
+ </VirtualTable>
51
+
52
+ <Modal ref="setting" width="700" height="600">
53
+ <template v-slot:head>
54
+ <div class="relative p-5">
55
+ <h3>Settings</h3>
56
+ <div class="absolute top-0 right-0 p-2">
57
+ <button type="button" class="p-2" @click="$refs.setting.close()">
58
+ <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">
59
+ <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"/>
60
+ </svg>
61
+ </button>
62
+ </div>
63
+ </div>
64
+ </template>
65
+ <ListViewSettings class="flex-1"
66
+ ref="setting"
67
+ :config="config"
68
+ @change="load" />
69
+ </Modal>
70
+
71
+ </div>
72
+ </template>
73
+
74
+ <script>
75
+
76
+ import throttle from "lodash/throttle";
77
+
78
+ export default{
79
+
80
+ emits: [ ],
81
+
82
+ props: {
83
+
84
+ src: String,
85
+
86
+ model: String,
87
+ name: String,
88
+ channel: String,
89
+
90
+ config: {
91
+ type: Object,
92
+ defaultValue: {}
93
+ },
94
+ configStore: String,
95
+
96
+ title: String
97
+
98
+ },
99
+
100
+ methods: {
101
+
102
+ clearSearch(){
103
+ this.preset.search = ''
104
+ this.load()
105
+ },
106
+
107
+ colOf(key){
108
+ return 'col-' + key
109
+ },
110
+
111
+ getHeader(column){
112
+
113
+ return [
114
+ this.$style.header,
115
+ (this.preset.filters ?? []).findIndex((_) => _.key === column.key && _.enabled) >= 0 ?
116
+ this.$style.headerSelected : ''
117
+ ]
118
+ .join(' ')
119
+ },
120
+
121
+ load(){
122
+ if(!this.src) return
123
+
124
+ this.socketEmit2(this.src, { preset: this.preset })
125
+ .then((res) => {
126
+ Object.assign(this.$data, res)
127
+ })
128
+ },
129
+
130
+ loadNext(){
131
+
132
+ this.$refs.table1.setState(2)
133
+ this.socketEmit2(this.src, {
134
+ preset: this.preset,
135
+ afterItem: this.items[this.items.length - 1]
136
+ })
137
+ .then((res) => {
138
+ this.items.push(...res.items)
139
+ this.hasNext = res.hasNext
140
+ console.log('loadNext', this.$data)
141
+ })
142
+ .catch((err) => {
143
+ this.toast(err)
144
+ })
145
+ .finally(() => this.$refs.table1.setState(1))
146
+ },
147
+
148
+ async loadConfig(){
149
+ switch(this.configStoreObj.type){
150
+ case 'socket':
151
+ return this.socketEmit2(this.configStoreObj.src, { key:this.name })
152
+ .then((config) => {
153
+ Object.assign(this.config, config ?? {})
154
+ this.configLoaded = true
155
+ })
156
+ }
157
+ },
158
+
159
+ saveConfig: throttle(function() {
160
+ if(!this.configLoaded) return
161
+ switch(this.configStoreObj.type){
162
+ case 'socket':
163
+ this.socketEmit2(this.configStoreObj.src, { key:this.name, config:this.config })
164
+ break
165
+ }
166
+ }, 1000, { leading:true }),
167
+
168
+ },
169
+
170
+
171
+ inject: [ 'socketEmit2', 'toast', 'alert' ],
172
+
173
+ computed: {
174
+
175
+ configStoreObj(){
176
+ const splitted = ((this.configStore ?? '').toString()).split(':')
177
+
178
+ return {
179
+ type: splitted[0],
180
+ src: splitted[1]
181
+ }
182
+ },
183
+
184
+ contentSlots(){
185
+ const slots = {}
186
+ for(let key in this.$slots){
187
+ if(!key.startsWith('col-'))
188
+ slots[key] = this.$slots[key]
189
+ }
190
+ return slots
191
+ },
192
+
193
+ headerSlots(){
194
+ const slots = {}
195
+ for(let key in this.$slots){
196
+ if(key.startsWith('col-'))
197
+ slots[key] = this.$slots[key]
198
+ }
199
+ return slots
200
+ },
201
+
202
+ preset(){
203
+ return ((this.config ?? {}).presets ?? [])[(this.config ?? {}).presetIdx]
204
+ },
205
+
206
+ presetColumns(){
207
+ if(!this.preset.columns) return
208
+
209
+ for(let i = 0 ; i < (this.preset.columns).length ; i++){
210
+ const presetColumn = this.preset.columns[i]
211
+ const column = this.config.columns[presetColumn.key] ?? {}
212
+ for(let key in column){
213
+ if(!presetColumn[key]){
214
+ this.preset.columns[i][key] = column[key]
215
+ }
216
+ }
217
+ }
218
+
219
+ return this.preset.columns
220
+ },
221
+
222
+ },
223
+
224
+ data(){
225
+ return {
226
+ items: [],
227
+ hasNext: false,
228
+ count: null,
229
+ configLoaded: false
230
+ }
231
+ },
232
+
233
+
234
+ mounted() {
235
+ this.loadConfig().then(() => {
236
+ this.load()
237
+ })
238
+ },
239
+
240
+ watch: {
241
+
242
+ config: {
243
+ deep: true,
244
+ handler(to){
245
+ this.saveConfig()
246
+ }
247
+ }
248
+
249
+ }
250
+
251
+ }
252
+
253
+ </script>
254
+
255
+ <style module>
256
+
257
+ .comp{
258
+ @apply flex-1 flex flex-col gap-4;
259
+ }
260
+
261
+ .header{
262
+ @apply p-2 cursor-pointer border-b-[2px] border-transparent overflow-hidden;
263
+ }
264
+ .header>*:first-child{
265
+ @apply text-ellipsis whitespace-nowrap overflow-x-hidden;
266
+ }
267
+
268
+ </style>
@@ -0,0 +1,598 @@
1
+ <template>
2
+ <div :class="styleComp">
3
+ <div>
4
+
5
+ <div class="p-6">
6
+ <h3>Presets</h3>
7
+ <br />
8
+ </div>
9
+
10
+ <div class="flex-1 overflow-y-auto border-t-[1px] border-text-50">
11
+ <div v-for="(preset, idx) in config.presets"
12
+ class="px-6 py-3 flex flex-row gap-2 items-center border-b-[1px] border-text-50 hover:bg-text-50 cursor-pointer">
13
+ <div class="px-2">
14
+ <Checkbox :checked="idx === config.presetIdx" @change="selectPreset(preset)" />
15
+ </div>
16
+ <div @click="presetOpenIdx = idx" class="flex-1">
17
+ <label>{{ preset.name }}</label>
18
+ </div>
19
+ <button class="px-2" v-if="config.presets.length > 1" @click="removePreset(idx)">
20
+ <svg width="16" height="16" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
21
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M10 10.25C10.4142 10.25 10.75 10.5858 10.75 11V16C10.75 16.4142 10.4142 16.75 10 16.75C9.58579 16.75 9.25 16.4142 9.25 16V11C9.25 10.5858 9.58579 10.25 10 10.25Z"/>
22
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M14 10.25C14.4142 10.25 14.75 10.5858 14.75 11V16C14.75 16.4142 14.4142 16.75 14 16.75C13.5858 16.75 13.25 16.4142 13.25 16V11C13.25 10.5858 13.5858 10.25 14 10.25Z"/>
23
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M10 2.25C8.48122 2.25 7.25 3.48122 7.25 5V5.25H3C2.58579 5.25 2.25 5.58579 2.25 6C2.25 6.41421 2.58579 6.75 3 6.75H4.25V19C4.25 20.5188 5.48122 21.75 7 21.75H17C18.5188 21.75 19.75 20.5188 19.75 19V6.75H21C21.4142 6.75 21.75 6.41421 21.75 6C21.75 5.58579 21.4142 5.25 21 5.25H16.75V5C16.75 3.48122 15.5188 2.25 14 2.25H10ZM15.25 5.25V5C15.25 4.30964 14.6904 3.75 14 3.75H10C9.30964 3.75 8.75 4.30964 8.75 5V5.25H15.25ZM5.75 6.75V19C5.75 19.6904 6.30964 20.25 7 20.25H17C17.6904 20.25 18.25 19.6904 18.25 19V6.75H5.75Z"/>
24
+ </svg>
25
+ </button>
26
+ </div>
27
+ <div class="p-5 text-center">
28
+ <button @click="addPreset">
29
+ <svg width="21" height="21" viewBox="0 0 24 24" class="fill-text-300 hover:fill-primary" xmlns="http://www.w3.org/2000/svg">
30
+ <path d="M12.75 5C12.75 4.58579 12.4142 4.25 12 4.25C11.5858 4.25 11.25 4.58579 11.25 5V11.25H5C4.58579 11.25 4.25 11.5858 4.25 12C4.25 12.4142 4.58579 12.75 5 12.75H11.25V19C11.25 19.4142 11.5858 19.75 12 19.75C12.4142 19.75 12.75 19.4142 12.75 19V12.75H19C19.4142 12.75 19.75 12.4142 19.75 12C19.75 11.5858 19.4142 11.25 19 11.25H12.75V5Z"/>
31
+ </svg>
32
+ </button>
33
+ </div>
34
+ </div>
35
+
36
+ </div>
37
+
38
+ <div>
39
+ <div class="p-6 pb-0">
40
+ <div class="flex flex-row gap-4 mb-3">
41
+ <button @click="presetOpenIdx = -1">
42
+ <svg width="19" height="19" viewBox="0 0 24 24" class="fill-text-300 hover:fill-text-400" xmlns="http://www.w3.org/2000/svg">
43
+ <path d="M10.5303 17.9697C10.8232 18.2626 10.8232 18.7374 10.5303 19.0303C10.2374 19.3232 9.76253 19.3232 9.46964 19.0303L3.67675 13.2374C2.99333 12.554 2.99333 11.446 3.67675 10.7626L9.46964 4.96967C9.76253 4.67678 10.2374 4.67678 10.5303 4.96967C10.8232 5.26256 10.8232 5.73744 10.5303 6.03033L5.31063 11.25H20C20.4142 11.25 20.75 11.5858 20.75 12C20.75 12.4142 20.4142 12.75 20 12.75H5.31063L10.5303 17.9697Z"/>
44
+ </svg>
45
+ </button>
46
+ <input class="flex-1 text-xl bg-transparent outline-none" :value="preset.name" @blur="preset.name = $event.target.value"
47
+ @keyup.enter="preset.name = $event.target.value" />
48
+ </div>
49
+
50
+ <div class="flex justify-center">
51
+ <Tabs v-model="presetTab" :items="tabItems" @change="tabChanged"/>
52
+ </div>
53
+ </div>
54
+
55
+ <div class="flex-1 overflow-y-auto px-6 mb-6">
56
+
57
+ <div class="p-2 mt-6 flex flex-col" v-if="presetTab === 'column'">
58
+ <ListItem :items="presetColumns" @reorder="reorderColumns">
59
+ <template v-slot="{ item }">
60
+ <div class="flex flex-row items-center gap-2" :key="item">
61
+ <div class="cursor-move" data-reorder>
62
+ <svg class="fill-text-200" width="21" height="21" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
63
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M4 16C4 15.4477 4.44772 15 5 15H19C19.5523 15 20 15.4477 20 16C20 16.5523 19.5523 17 19 17H5C4.44772 17 4 16.5523 4 16Z"/>
64
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M4 8C4 7.44772 4.44772 7 5 7H19C19.5523 7 20 7.44772 20 8C20 8.55228 19.5523 9 19 9H5C4.44772 9 4 8.55228 4 8Z"/>
65
+ </svg>
66
+ </div>
67
+ <Checkbox v-model="item.visible" :true-value="true" :false-value="false" @change="$emit('change')">
68
+ {{ item.label ?? item.key }}
69
+ </Checkbox>
70
+ </div>
71
+ </template>
72
+ </ListItem>
73
+ </div>
74
+
75
+ <div class="flex flex-col" v-else-if="presetTab === 'filter'">
76
+ <div v-if="filterableColumns.length > 0">
77
+ <ListPage1Filter v-if="preset.filters" v-for="filter in preset.filters"
78
+ :filter="filter" :column="config.columns[filter.key]"
79
+ @remove="removeFilter(filter)" @change="$emit('change')"/>
80
+ <div class="py-8">
81
+ <Dropdown @change="addFilter" v-model="presetFilterSelector">
82
+ <option value="" disabled selected>{{ $t('Add Filter')}}</option>
83
+ <option v-for="column in filterableColumns" :value="column.key">{{ column.label ?? column.key }}</option>
84
+ </Dropdown>
85
+ </div>
86
+ </div>
87
+ <div v-else>
88
+ <div class="p-6 text-center">
89
+ <label>Filter not available</label>
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ <div class="flex flex-col" v-else-if="presetTab === 'sort'">
95
+ <div v-if="sortableColumns.length > 0" class="my-8">
96
+ <div v-for="sort in preset.sorts" class="py-4">
97
+ <div class="flex flex-row items-center gap-2">
98
+ <div class="flex-1">
99
+ <Dropdown v-model="sort.key" @change="$emit('change')">
100
+ <option value="" disabled selected>{{ $t('Add Sort') }}</option>
101
+ <option v-for="column in sortableColumns" :value="column.key">{{ column.label ?? column.key }}</option>
102
+ </Dropdown>
103
+ </div>
104
+ <div>
105
+ <Dropdown v-model="sort.type" @change="$emit('change')" class="w-[80px]">
106
+ <option value="">Asc</option>
107
+ <option value="desc">Desc</option>
108
+ </Dropdown>
109
+ </div>
110
+ <button @click="removeSort(sort)">
111
+ <svg width="21" height="21" class="fill-text-100 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
112
+ <path class="secondary" fill-rule="evenodd" d="M15.78 14.36a1 1 0 0 1-1.42 1.42l-2.82-2.83-2.83 2.83a1 1 0 1 1-1.42-1.42l2.83-2.82L7.3 8.7a1 1 0 0 1 1.42-1.42l2.83 2.83 2.82-2.83a1 1 0 0 1 1.42 1.42l-2.83 2.83 2.83 2.82z"/>
113
+ </svg>
114
+ </button>
115
+ </div>
116
+ </div>
117
+ <div v-if="preset.sorts && preset.sorts.length > 0" class="h-[1px] my-4 bg-text-100"></div>
118
+ <div class="py-4">
119
+ <Dropdown @change="addSort" v-model="presetSortSelector">
120
+ <option value="" disabled selected>{{ $t('Add Sort') }}</option>
121
+ <option v-for="column in sortableColumns" :value="column.key">{{ column.label ?? column.key }}</option>
122
+ </Dropdown>
123
+ </div>
124
+ </div>
125
+ <div v-else>
126
+ <div class="p-6 text-center">
127
+ <label>Sort not available</label>
128
+ </div>
129
+ </div>
130
+ </div>
131
+
132
+ <div class="flex flex-col gap-3 py-8" v-else-if="presetTab === 'summary'">
133
+
134
+ <div v-if="config.summaryOpenIdx === -1">
135
+ <div v-for="(summary, idx) in preset.summaries">
136
+ <div class="flex flex-row items-center gap-3 border-text-50 border-[1px] p-2 rounded-lg">
137
+ <div class="px-2">
138
+ <Checkbox v-model="summary.enabled" @change="enableSummary(idx)" />
139
+ </div>
140
+ <div class="flex-1" @click="config.summaryOpenIdx = idx">
141
+ <label>{{ summary.title }}</label>
142
+ </div>
143
+ <button class="px-2" v-if="preset.summaries.length > 1" @click="removeSummary(summary)">
144
+ <svg width="16" height="16" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
145
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M10 10.25C10.4142 10.25 10.75 10.5858 10.75 11V16C10.75 16.4142 10.4142 16.75 10 16.75C9.58579 16.75 9.25 16.4142 9.25 16V11C9.25 10.5858 9.58579 10.25 10 10.25Z"/>
146
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M14 10.25C14.4142 10.25 14.75 10.5858 14.75 11V16C14.75 16.4142 14.4142 16.75 14 16.75C13.5858 16.75 13.25 16.4142 13.25 16V11C13.25 10.5858 13.5858 10.25 14 10.25Z"/>
147
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M10 2.25C8.48122 2.25 7.25 3.48122 7.25 5V5.25H3C2.58579 5.25 2.25 5.58579 2.25 6C2.25 6.41421 2.58579 6.75 3 6.75H4.25V19C4.25 20.5188 5.48122 21.75 7 21.75H17C18.5188 21.75 19.75 20.5188 19.75 19V6.75H21C21.4142 6.75 21.75 6.41421 21.75 6C21.75 5.58579 21.4142 5.25 21 5.25H16.75V5C16.75 3.48122 15.5188 2.25 14 2.25H10ZM15.25 5.25V5C15.25 4.30964 14.6904 3.75 14 3.75H10C9.30964 3.75 8.75 4.30964 8.75 5V5.25H15.25ZM5.75 6.75V19C5.75 19.6904 6.30964 20.25 7 20.25H17C17.6904 20.25 18.25 19.6904 18.25 19V6.75H5.75Z"/>
148
+ </svg>
149
+ </button>
150
+ </div>
151
+ </div>
152
+ <div class="p-2 text-center">
153
+ <button @click="addSummary">
154
+ <svg width="21" height="21" viewBox="0 0 24 24" class="fill-text-300 hover:fill-primary" xmlns="http://www.w3.org/2000/svg">
155
+ <path d="M12.75 5C12.75 4.58579 12.4142 4.25 12 4.25C11.5858 4.25 11.25 4.58579 11.25 5V11.25H5C4.58579 11.25 4.25 11.5858 4.25 12C4.25 12.4142 4.58579 12.75 5 12.75H11.25V19C11.25 19.4142 11.5858 19.75 12 19.75C12.4142 19.75 12.75 19.4142 12.75 19V12.75H19C19.4142 12.75 19.75 12.4142 19.75 12C19.75 11.5858 19.4142 11.25 19 11.25H12.75V5Z"/>
156
+ </svg>
157
+ </button>
158
+ </div>
159
+ </div>
160
+
161
+ <div v-if="config.summaryOpenIdx > -1">
162
+
163
+ <div class="flex flex-row items-center p-3 py-1">
164
+ <label class="text-text-400 flex-1">Enabled</label>
165
+ <Checkbox v-model="openedPresetSummary.enabled" @change="loadSummary"/>
166
+ </div>
167
+
168
+ <div class="h-[1px] bg-text-50 my-1"></div>
169
+
170
+ <div class="flex flex-row items-center p-3 py-1">
171
+ <label class="text-text-400 flex-1">{{ $t('Title') }}</label>
172
+ <Textbox v-model="openedPresetSummary.title" class="mt-1" />
173
+ </div>
174
+
175
+ <div class="h-[1px] bg-text-50 my-1"></div>
176
+
177
+ <div class="flex flex-row items-center p-3 py-1">
178
+ <label class="text-text-400 flex-1">Hide Details</label>
179
+ <Checkbox v-model="openedPresetSummary.hideDetails" @change="loadSummary"/>
180
+ </div>
181
+
182
+ <div class="h-[1px] bg-text-50 my-1"></div>
183
+
184
+ <div class="p-3">
185
+ <label class="text-text-400 flex-1">Type</label>
186
+ <div class="mt-2">
187
+ <Dropdown v-model="openedPresetSummary.mode" class="flex-1" @change="loadSummary">
188
+ <option value="table">{{ $t('Table') }}</option>
189
+ <option value="bar">{{ $t('Bar Chart') }}</option>
190
+ <option value="line">{{ $t('Line Chart') }}</option>
191
+ <option value="map">{{ $t('Maps') }}</option>
192
+ </Dropdown>
193
+ </div>
194
+ <div v-if="[ 'bar', 'line' ].includes(openedPresetSummary.mode)" class="mt-1">
195
+ <Checkbox v-model="openedPresetSummary.hideLegends" @change="loadSummary">Hide Legend</Checkbox>
196
+ </div>
197
+ </div>
198
+
199
+ <div v-if="openedPresetSummary.mode === 'table'">
200
+
201
+ <div class="p-3">
202
+ <label class="text-text-400 flex-1">{{ $t('Row') }}</label>
203
+ <div class="flex flex-row mt-2 gap-2">
204
+ <Dropdown v-model="openedPresetSummary.table.rows[0].key" class="flex-1" @change="loadSummary">
205
+ <option value="" disabled selected>{{ $t('Add Column') }}</option>
206
+ <option v-for="column in filterableColumns" :value="column.key">{{ column.label ?? column.key }}</option>
207
+ </Dropdown>
208
+ <Dropdown v-model="openedPresetSummary.table.rows[0].format" class="w-[100px]" @change="loadSummary">
209
+ <option value="">{{ $t('Default') }}</option>
210
+ <option value="date">{{ $t('Date') }}</option>
211
+ <option value="month">{{ $t('Month') }}</option>
212
+ <option value="quarter">{{ $t('Quarterly') }}</option>
213
+ <option value="year">{{ $t('Year') }}</option>
214
+ </Dropdown>
215
+ </div>
216
+ <Checkbox class="mt-2" v-model="openedPresetSummary.table.showRowTotal">
217
+ Row Total
218
+ </Checkbox>
219
+ </div>
220
+
221
+ <div class="p-3">
222
+ <label class="text-text-400 flex-1">{{$t('Column') }}</label>
223
+ <div>
224
+ <Dropdown v-model="openedPresetSummary.table.columns[0].key" class="flex-1" @change="loadSummary">
225
+ <option value="" disabled selected>{{ $t('Add Column') }}</option>
226
+ <option value="(none)">{{ $t('(None)') }}</option>
227
+ <option v-for="column in filterableColumns" :value="column.key">{{ column.label ?? column.key }}</option>
228
+ </Dropdown>
229
+ </div>
230
+ <Checkbox class="mt-2" v-model="openedPresetSummary.table.showColumnTotal">
231
+ Column Total
232
+ </Checkbox>
233
+ </div>
234
+
235
+ <div class="p-3">
236
+ <label class="text-text-400 flex-1">Values</label>
237
+ <div class="flex flex-row mt-2 gap-2">
238
+ <Dropdown v-model="openedPresetSummary.table.values[0].aggregrate" class="flex-1" @change="loadSummary">
239
+ <option value="" disabled selected>{{ $t('Select') }}</option>
240
+ <option value="count">{{ $t('Count') }}</option>
241
+ <option value="max">{{ $t('Max') }}</option>
242
+ <option value="min">{{ $t('Min') }}</option>
243
+ <option value="avg">{{ $t('Avg') }}</option>
244
+ </Dropdown>
245
+ </div>
246
+ </div>
247
+
248
+ </div>
249
+
250
+ <div v-else-if="openedPresetSummary.mode === 'bar'">
251
+
252
+ <div class="p-3">
253
+ <label class="text-text-400 flex-1">{{ $t('X-axis') }}</label>
254
+ <div class="flex flex-row mt-2 gap-2">
255
+ <Dropdown v-model="openedPresetSummary.bar.rows[0].key" class="flex-1" @change="loadSummary">
256
+ <option value="" disabled selected>{{ $t('Add Column') }}</option>
257
+ <option v-for="column in filterableColumns" :value="column.key">{{ column.label ?? column.key }}</option>
258
+ </Dropdown>
259
+ <Dropdown v-model="openedPresetSummary.bar.rows[0].format" class="w-[100px]" @change="loadSummary">
260
+ <option value="">{{ $t('Default') }}</option>
261
+ <option value="date">{{ $t('Date') }}</option>
262
+ <option value="month">{{ $t('Month') }}</option>
263
+ <option value="quarter">{{ $t('Quarterly') }}</option>
264
+ <option value="year">{{ $t('Year') }}</option>
265
+ </Dropdown>
266
+ </div>
267
+ </div>
268
+
269
+ <div class="p-3">
270
+ <label class="text-text-400 flex-1">{{ $t('Column') }}</label>
271
+ <div class="flex flex-row mt-2 gap-2">
272
+ <Dropdown v-model="openedPresetSummary.bar.columns[0].key" class="flex-1" @change="loadSummary">
273
+ <option value="" disabled selected>{{ $t('Add Column') }}</option>
274
+ <option value="(none)">{{ $t('None') }}</option>
275
+ <option v-for="column in filterableColumns" :value="column.key">{{ column.label ?? column.key }}</option>
276
+ </Dropdown>
277
+ <Dropdown v-model="openedPresetSummary.bar.columns[0].format" class="w-[100px]" @change="loadSummary">
278
+ <option value="">{{ $t('Default') }}</option>
279
+ <option value="date">{{ $t('Date') }}</option>
280
+ </Dropdown>
281
+ </div>
282
+ <Checkbox class="mt-2" v-model="openedPresetSummary.bar.stacked">
283
+ Stacked
284
+ </Checkbox>
285
+ </div>
286
+
287
+ <div class="p-3">
288
+ <label class="text-text-400 flex-1">Values</label>
289
+ <div class="flex flex-row mt-2 gap-2">
290
+ <Dropdown v-model="openedPresetSummary.bar.values[0].aggregrate" class="flex-1" @change="loadSummary">
291
+ <option value="" disabled selected>{{ $t('Select') }}</option>
292
+ <option value="count">{{ $t('Count') }}</option>
293
+ <option value="max">{{ $t('Max') }}</option>
294
+ <option value="min">{{ $t('Min') }}</option>
295
+ <option value="avg">{{ $t('Avg') }}</option>
296
+ </Dropdown>
297
+ </div>
298
+ </div>
299
+
300
+ </div>
301
+
302
+ <div v-else-if="openedPresetSummary.mode === 'line'">
303
+
304
+ <div class="p-3">
305
+ <label class="text-text-400 flex-1">{{ $t('X-axis') }}</label>
306
+ <div class="flex flex-row mt-2 gap-2">
307
+ <Dropdown v-model="openedPresetSummary.line.rows[0].key" class="flex-1" @change="loadSummary">
308
+ <option value="" disabled selected>{{ $t('Add Column') }}</option>
309
+ <option v-for="column in filterableColumns" :value="column.key">{{ column.label ?? column.key }}</option>
310
+ </Dropdown>
311
+ <Dropdown v-model="openedPresetSummary.line.rows[0].format" class="w-[100px]" @change="loadSummary">
312
+ <option value="">{{ $t('Default') }}</option>
313
+ <option value="date">{{ $t('Date') }}</option>
314
+ <option value="month">{{ $t('Month') }}</option>
315
+ <option value="quarter">{{ $t('Quarterly') }}</option>
316
+ <option value="year">{{ $t('Year') }}</option>
317
+ </Dropdown>
318
+ </div>
319
+ </div>
320
+
321
+ <div class="p-3">
322
+ <label class="text-text-400 flex-1">{{$t('Column') }}</label>
323
+ <div class="flex flex-row mt-2 gap-2">
324
+ <Dropdown v-model="openedPresetSummary.line.columns[0].key" class="flex-1" @change="loadSummary">
325
+ <option value="" disabled selected>{{ $t('Add Column') }}</option>
326
+ <option value="(none)">{{ $t('None') }}</option>
327
+ <option v-for="column in filterableColumns" :value="column.key">{{ column.label ?? column.key }}</option>
328
+ </Dropdown>
329
+ <Dropdown v-model="openedPresetSummary.line.columns[0].format" class="w-[100px]" @change="loadSummary">
330
+ <option value="">{{ $t('Default') }}</option>
331
+ <option value="date">{{ $t('Date') }}</option>
332
+ </Dropdown>
333
+ </div>
334
+ </div>
335
+
336
+ <div class="p-3">
337
+ <label class="text-text-400 flex-1">Values</label>
338
+ <div class="flex flex-row mt-2 gap-2">
339
+ <Dropdown v-model="openedPresetSummary.line.values[0].aggregrate" class="flex-1" @change="loadSummary">
340
+ <option value="" disabled selected>{{ $t('Select') }}</option>
341
+ <option value="count">{{ $t('Count') }}</option>
342
+ <option value="max">{{ $t('Max') }}</option>
343
+ <option value="min">{{ $t('Min') }}</option>
344
+ <option value="avg">{{ $t('Avg') }}</option>
345
+ </Dropdown>
346
+ </div>
347
+ </div>
348
+
349
+ </div>
350
+
351
+ <div v-else-if="openedPresetSummary.mode === 'map'">
352
+
353
+ <div class="p-3 flex flex-row gap-3">
354
+ <div class="flex-1">
355
+ <label class="text-text-400 flex-1">Map Type</label>
356
+ <Dropdown class="mt-2" v-model="openedPresetSummary.map.mapType" @change="loadSummary">
357
+ <option value="" disabled selected>(Default)</option>
358
+ <option value="heatmap">Heatmap</option>
359
+ </Dropdown>
360
+ </div>
361
+ <div>
362
+ <label class="text-text-400 flex-1">Radius</label>
363
+ <div class="flex flex-row mt-2 gap-2">
364
+ <Textbox v-model="openedPresetSummary.map.mapRadius" class="w-[60px]" @blur="loadSummary" @keyup.enter="loadSummary" />
365
+ </div>
366
+ </div>
367
+ </div>
368
+
369
+ <div class="p-3">
370
+ <label class="text-text-400 flex-1">{{$t('Property') }}</label>
371
+ <div>
372
+ <Dropdown v-model="openedPresetSummary.map.key" class="flex-1" @change="loadSummary">
373
+ <option value="" disabled selected>{{ $t('Select Column') }}</option>
374
+ <option v-for="column in coordinateColumns" :value="column.key">{{ column.label ?? column.key }}</option>
375
+ </Dropdown>
376
+ </div>
377
+ </div>
378
+
379
+ </div>
380
+
381
+ </div>
382
+
383
+ </div>
384
+
385
+ </div>
386
+ </div>
387
+
388
+ </div>
389
+ </template>
390
+
391
+ <script>
392
+
393
+ export default{
394
+
395
+ emits: [ 'change' ],
396
+
397
+ props: {
398
+ config: Object
399
+ },
400
+
401
+ methods: {
402
+
403
+ addFilter(key){
404
+
405
+ const column = this.config.columns[key]
406
+
407
+ if(!this.preset.filters){
408
+ this.preset.filters = []
409
+ }
410
+
411
+ let filters = [{}]
412
+ switch(column.type){
413
+ case 'date':
414
+ case 'enum':
415
+ filters = [{ value:[] }]
416
+ break
417
+ }
418
+
419
+ this.preset.filters.push({
420
+ enabled: true,
421
+ key: column.key,
422
+ label: column.label,
423
+ type: column.type,
424
+ typeParams: column.typeParams,
425
+ filters
426
+ })
427
+
428
+ this.presetFilterSelector = null
429
+ },
430
+
431
+ addSort(key){
432
+
433
+ const column = this.preset.columns.filter((_) => _.key === key).pop()
434
+
435
+ if(!this.preset.sorts){
436
+ this.preset.sorts = []
437
+ }
438
+
439
+ this.preset.sorts.push({
440
+ key: column.key,
441
+ label: column.label
442
+ })
443
+
444
+ this.presetSortSelector = null
445
+
446
+ this.$emit('change')
447
+ },
448
+
449
+ removeFilter(filter){
450
+ this.preset.filters.splice(this.preset.filters.indexOf(filter), 1)
451
+ this.$emit('change')
452
+ },
453
+
454
+ removeSort(sort){
455
+ this.preset.sorts.splice(this.preset.sorts.indexOf(sort), 1)
456
+ this.$emit('change')
457
+ },
458
+
459
+ reorderColumns(from, to){
460
+ this.preset.columns.splice(to, 0, this.preset.columns.splice(from, 1)[0])
461
+ },
462
+
463
+ selectPreset(preset){
464
+ this.config.presetIdx = this.config.presets.findIndex((_) => _ === preset)
465
+ this.$emit('change')
466
+ },
467
+
468
+ tabChanged(value){
469
+ if(value === 'summary'){
470
+ this.config.summaryOpenIdx = -1
471
+ }
472
+ },
473
+
474
+ },
475
+
476
+
477
+ inject: [],
478
+
479
+ computed: {
480
+
481
+ styleComp(){
482
+ return [
483
+ this.$style.comp,
484
+ this.$style['comp-' + this.viewMode],
485
+ this.presetOpenIdx >= 0 ? this.$style['isOpen'] : ''
486
+ ]
487
+ .join(' ')
488
+ },
489
+
490
+ filterableColumns(){
491
+ return Object.values(this.config.columns).filter((_) => _.filterable)
492
+ },
493
+
494
+ sortableColumns(){
495
+ return Object.values(this.config.columns).filter((_) => _.sortable)
496
+ },
497
+
498
+ preset(){
499
+ return this.config.presets[this.config.presetIdx] ?? {}
500
+ },
501
+
502
+ presetColumns(){
503
+ if(!this.preset.columns) return
504
+
505
+ for(let i = 0 ; i < (this.preset.columns).length ; i++){
506
+ const presetColumn = this.preset.columns[i]
507
+ const column = this.config.columns[presetColumn.key] ?? {}
508
+ for(let key in column){
509
+ if(!presetColumn[key]){
510
+ this.preset.columns[i][key] = column[key]
511
+ }
512
+ }
513
+ }
514
+
515
+ return this.preset.columns
516
+ },
517
+
518
+ presetAppearances(){
519
+ return this.preset.appearances ?? {}
520
+ },
521
+
522
+ presetSortedColumns(){
523
+ const c = {};
524
+ (this.preset.sorts ?? []).forEach((_) => c[_.key] = _.type);
525
+ return c
526
+ },
527
+
528
+ presetCurrentFilters(){
529
+ if(!this.preset.filters) return []
530
+ return this.preset.filters.filter((_) => _.key === this.selectedColumn)
531
+ },
532
+
533
+ openedPresetSummary(){
534
+ return ((this.preset ?? {}).summaries ?? [])[this.config.summaryOpenIdx] ?? undefined
535
+ },
536
+
537
+ },
538
+
539
+ data(){
540
+ return {
541
+ tabItems: [
542
+ { text:'Columns', value:'column' },
543
+ { text:'Filters', value:'filter' },
544
+ { text:'Sorts', value:'sort' },
545
+ { text:'Summary', value:'summary' },
546
+ ],
547
+ presetTab: 'column',
548
+ presetFilterSelector: null,
549
+ presetSortSelector: null,
550
+ viewMode: null,
551
+ presetOpenIdx: -1,
552
+ }
553
+ },
554
+
555
+ mounted() {
556
+ this.viewMode = this.$el.clientWidth <= 480 ? 'sm' : 'md'
557
+ },
558
+
559
+ watch: {
560
+
561
+
562
+ }
563
+
564
+ }
565
+
566
+ </script>
567
+
568
+ <style module>
569
+
570
+ .comp{
571
+ }
572
+
573
+ .comp-sm{
574
+ }
575
+ .comp-sm>*:first-child{
576
+
577
+ }
578
+ .comp-sm>*:last-child{
579
+ @apply hidden;
580
+ }
581
+ .comp-sm.isOpen>*:first-child{
582
+ @apply hidden;
583
+ }
584
+ .comp-sm.isOpen>*:last-child{
585
+ @apply block;
586
+ }
587
+
588
+ .comp-md{
589
+ @apply flex flex-row gap-4;
590
+ }
591
+ .comp-md>*:first-child{
592
+ @apply w-[200px];
593
+ }
594
+ .comp-md>*:last-child{
595
+ @apply flex-1 flex flex-col;
596
+ }
597
+
598
+ </style>
@@ -0,0 +1,219 @@
1
+ <template>
2
+ <div :class="$style.comp">
3
+
4
+ <div :class="$style.pageGrid" ref="pageGrid">
5
+ <div v-for="(widget, index) in widgets"
6
+ :class="$style.column + ` col-span-${widget.span}`"
7
+ :style="{ height:widget.height }"
8
+ @mouseover="onMoverMouseOver">
9
+ <div :class="$style.mover" class="hidden" @mousedown="(e) => onMoverStart(e, widget, index)"></div>
10
+ <div :class="$style.resizeRight" @mousedown="(e) => onMouseDown(e, widget, 'right')"></div>
11
+ <div :class="$style.resizeBottom" @mousedown="(e) => onMouseDown(e, widget, 'bottom')"></div>
12
+ <div :class="$style.resizeBottomRight" @mousedown="(e) => onMouseDown(e, widget, 'bottom-right')"></div>
13
+ <div v-if="widget.title">
14
+ <h5>{{ widget.title }}</h5>
15
+ </div>
16
+ <component :is="widget.type" :="widget.props"></component>
17
+ </div>
18
+ </div>
19
+
20
+ <div class="col-span-1"></div>
21
+ <div class="col-span-2"></div>
22
+ <div class="col-span-3"></div>
23
+ <div class="col-span-4"></div>
24
+ <div class="col-span-5"></div>
25
+ <div class="col-span-6"></div>
26
+ <div class="col-span-7"></div>
27
+ <div class="col-span-8"></div>
28
+ <div class="col-span-9"></div>
29
+ <div class="col-span-10"></div>
30
+ <div class="col-span-11"></div>
31
+
32
+ <div class="col-span-12"></div>
33
+
34
+ </div>
35
+ </template>
36
+
37
+ <script>
38
+
39
+ export default{
40
+
41
+ props: {
42
+
43
+ widgets: Array
44
+
45
+ },
46
+
47
+ methods: {
48
+
49
+ onMouseDown(e, widget){
50
+ this.curResize = {
51
+ target: e.target.parentNode,
52
+ startX: e.clientX,
53
+ startY: e.clientY,
54
+ startSpan: widget.span,
55
+ startHeight: widget.height,
56
+ xSize: Math.round(window.innerWidth / 12),
57
+ ySize: Math.round(window.innerHeight / 12),
58
+ widget
59
+ }
60
+
61
+ window.addEventListener('mousemove', this.onMouseMove)
62
+ window.addEventListener('mouseup', this.onMouseOut)
63
+ },
64
+
65
+ onMouseMove(e){
66
+ if(!this.curResize) return
67
+
68
+ e.preventDefault()
69
+
70
+ const distanceX = e.clientX - this.curResize.startX
71
+ const distanceY = e.clientY - this.curResize.startY
72
+
73
+ const spanAdder = Math.round(distanceX / this.curResize.xSize)
74
+ const heightAdder = Math.round(distanceY / this.curResize.ySize) * this.curResize.ySize
75
+
76
+ let span = parseInt(this.curResize.startSpan) + spanAdder
77
+ let height = (parseInt(this.curResize.startHeight) + heightAdder) + 'px'
78
+
79
+ if(span < 1) span = 1
80
+ else if(span > 12) span = 12
81
+
82
+ if(height < this.curResize.ySize) height = this.curResize.ySize
83
+ else if(height > window.innerHeight) height = window.innerHeight
84
+
85
+ this.curResize.widget.span = span
86
+ this.curResize.widget.height = height
87
+ },
88
+
89
+ onMouseOut(){
90
+ window.removeEventListener('mousemove', this.onMouseMove)
91
+ window.removeEventListener('mouseup', this.onMouseOut)
92
+ this.curResize = null
93
+ },
94
+
95
+ onMoverStart(e, widget, index){
96
+
97
+ const columnEl = e.target.parentNode;
98
+ const rect = columnEl.getBoundingClientRect()
99
+
100
+ const cloned = columnEl.cloneNode(true)
101
+ cloned.classList.add(this.$style.dragged)
102
+ cloned.style.left = (Math.round(rect.x) + 16) + 'px'
103
+ cloned.style.top = (Math.round(rect.y) + 16) + 'px'
104
+ cloned.style.width = columnEl.clientWidth + 'px'
105
+ cloned.style.height = columnEl.clientHeight + 'px'
106
+ this.$el.appendChild(cloned)
107
+
108
+ this.curResize = {
109
+ widget,
110
+ index,
111
+ cloned,
112
+ startX: e.clientX,
113
+ startY: e.clientY
114
+ }
115
+
116
+ window.addEventListener('mousemove', this.onMoverMove)
117
+ window.addEventListener('mouseup', this.onMoverUp)
118
+ },
119
+
120
+ swapArray(arr, i, j){
121
+ const temp = arr[i];
122
+ arr[i] = arr[j];
123
+ arr[j] = temp;
124
+ },
125
+
126
+ onMoverMove(e){
127
+ if(!this.curResize) return
128
+
129
+ e.preventDefault()
130
+
131
+ const cloned = this.curResize.cloned
132
+ const distanceX = e.clientX - this.curResize.startX
133
+ const distanceY = e.clientY - this.curResize.startY
134
+ this.curResize.startX = e.clientX
135
+ this.curResize.startY = e.clientY
136
+
137
+ cloned.style.left = (parseInt(cloned.style.left) + distanceX) + 'px'
138
+ cloned.style.top = (parseInt(cloned.style.top) + distanceY) + 'px'
139
+ },
140
+
141
+ onMoverMouseOver(e){
142
+ if(!this.curResize || !this.curResize.cloned) return
143
+
144
+ const targetIdx = Array.prototype.indexOf.call(this.$refs.pageGrid.children, e.target.parentNode)
145
+
146
+ this.$refs.pageGrid.querySelectorAll('.' + this.$style.highlight).forEach(el => el.classList.remove(this.$style.highlight))
147
+
148
+ if(targetIdx !== this.curResize.index){
149
+ this.curResize.targetIdx = targetIdx
150
+ e.target.classList.add(this.$style.highlight)
151
+ }
152
+ },
153
+
154
+ onMoverUp(){
155
+ this.swapArray(this.widgets, this.curResize.targetIdx, this.curResize.index)
156
+
157
+ window.removeEventListener('mousemove', this.onMoverMove)
158
+ window.removeEventListener('mouseup', this.onMoverUp)
159
+ this.$el.removeChild(this.curResize.cloned)
160
+ this.curResize = null
161
+
162
+ this.$refs.pageGrid.querySelectorAll('.' + this.$style.highlight).forEach(el => el.classList.remove(this.$style.highlight))
163
+ },
164
+
165
+ test(){
166
+ this.swapArray(this.widgets, 4, 0)
167
+ }
168
+
169
+ },
170
+
171
+ data(){
172
+ return {
173
+ curResize: null
174
+ }
175
+ }
176
+
177
+ }
178
+
179
+ </script>
180
+
181
+ <style module>
182
+
183
+ .comp{
184
+ }
185
+
186
+ .column{
187
+ @apply flex flex-col gap-2;
188
+ }
189
+
190
+ .pageGrid{
191
+ @apply grid grid-cols-12 gap-6;
192
+ }
193
+ .pageGrid>*{
194
+ @apply relative flex;
195
+ }
196
+
197
+ .resizeRight{
198
+ @apply absolute top-0 right-0 w-[4px] bottom-[6px] bg-text-50 cursor-e-resize;
199
+ }
200
+ .resizeBottom{
201
+ @apply absolute bottom-0 left-0 right-[6px] h-[4px] bg-text-50 cursor-s-resize;
202
+ }
203
+ .resizeBottomRight{
204
+ @apply absolute bottom-0 right-0 w-[4px] h-[4px] bg-text-50 cursor-se-resize;
205
+ }
206
+
207
+ .mover{
208
+ @apply absolute top-0 left-0 w-[24px] h-[24px] bg-text-50 cursor-move;
209
+ }
210
+
211
+ .dragged{
212
+ @apply absolute bg-base-300;
213
+ }
214
+
215
+ .highlight{
216
+ @apply opacity-50;
217
+ }
218
+
219
+ </style>
package/src/index.js CHANGED
@@ -208,6 +208,7 @@ export default{
208
208
 
209
209
  app.component('Alert', defineAsyncComponent(() => import("./components/Alert.vue")))
210
210
  app.component('Button', defineAsyncComponent(() => import("./components/Button.vue")))
211
+ app.component('Box', defineAsyncComponent(() => import("./components/Box.vue")))
211
212
  app.component('SearchButton', defineAsyncComponent(() => import("./components/SearchButton.vue")))
212
213
  app.component('ButtonGroup', defineAsyncComponent(() => import("./components/ButtonGroup.vue")))
213
214
  app.component('ChatTyping', defineAsyncComponent(() => import("./components/ChatTyping.vue")))
@@ -231,10 +232,13 @@ export default{
231
232
  app.component('ListPage1', defineAsyncComponent(() => import("./components/ListPage1.vue")))
232
233
  app.component('ListPage1Filter', defineAsyncComponent(() => import("./components/ListPage1Filter.vue")))
233
234
  app.component('ListItem', defineAsyncComponent(() => import("./components/ListItem.vue")))
235
+ app.component('ListView', defineAsyncComponent(() => import("./components/ListView.vue")))
236
+ app.component('ListViewSettings', defineAsyncComponent(() => import("./components/ListViewSettings.vue")))
234
237
  app.component('Carousel', defineAsyncComponent(() => import("./components/Carousel.vue")))
235
238
  app.component('ContextMenu', defineAsyncComponent(() => import("./components/ContextMenu.vue")))
236
239
  app.component('Modal', defineAsyncComponent(() => import("./components/Modal.vue")))
237
240
  app.component('OTPField', defineAsyncComponent(() => import("./components/OTPField.vue")))
241
+ app.component('PageBuilder', defineAsyncComponent(() => import("./components/PageBuilder.vue")))
238
242
  app.component('Radio', defineAsyncComponent(() => import("./components/Radio.vue")))
239
243
  app.component('TableView', defineAsyncComponent(() => import("./components/TableView.vue")))
240
244
  app.component('TableViewHead', defineAsyncComponent(() => import("./components/TableViewHead.vue")))
@@ -252,4 +256,4 @@ export default{
252
256
 
253
257
 
254
258
  }
255
- }
259
+ }
@@ -209,8 +209,15 @@ function observeInit(){
209
209
  el.__observeKey = key
210
210
  }
211
211
 
212
+ const unregister = function(el){
213
+ if(el && el.__observeKey && observeCols[el.__observeKey]){
214
+ delete observeCols[el.__observeKey]
215
+ }
216
+ }
217
+
212
218
  return {
213
219
  once,
214
- always
220
+ always,
221
+ unregister
215
222
  }
216
223
  }