@mixd-id/web-scaffold 0.1.230406394 → 0.1.230406396

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.230406394",
4
+ "version": "0.1.230406396",
5
5
  "scripts": {
6
6
  "dev": "vite serve",
7
7
  "build": "vite build",
@@ -52,7 +52,7 @@
52
52
  "@tailwindcss/line-clamp": "^0.4.0",
53
53
  "@vueuse/core": "^9.0.2",
54
54
  "adm-zip": "^0.5.10",
55
- "axios": "^1.7.9",
55
+ "axios": "1.8.2",
56
56
  "bcrypt": "^5.1.1",
57
57
  "chart.js": "^4.2.1",
58
58
  "compression": "^1.7.4",
@@ -69,11 +69,11 @@
69
69
  "md5": "^2.3.0",
70
70
  "mitt": "^3.0.1",
71
71
  "nprogress": "^0.2.0",
72
- "pinia": "^2.3.0",
73
- "prismjs": "^1.28.0",
72
+ "pinia": "^2.0.2",
73
+ "prismjs": "1.30.0",
74
74
  "redis": "^4.6.13",
75
75
  "sequelize": "^6.37.5",
76
- "serve-static": "^1.15.0",
76
+ "serve-static": "2.1.0",
77
77
  "tailwindcss": "^3.2.4",
78
78
  "vue": "^3.2.25",
79
79
  "vue-chartjs": "^5.2.0",
@@ -18,7 +18,7 @@
18
18
  <div v-else :class="compClass">
19
19
 
20
20
  <div v-if="mode === 'popup'" ref="popup"
21
- @click="!readonly ? contextMenu = { caller:$refs.popup, value:this.modelValue } : null"
21
+ @click="!readonly ? contextMenu = { caller:$refs.popup, value:this.mModelValue } : null"
22
22
  class="flex-1">
23
23
  <input class="flex-1" type="text" readonly :value="DMMMYYYY"/>
24
24
  <div :class="$style.arrow" v-if="!readonly">
@@ -70,11 +70,14 @@
70
70
  <div class="grid grid-cols-7 gap-2 mt-2">
71
71
  <div v-for="i in 7">{{ getDayOfWeekLabel(i) }}</div>
72
72
  <button type="button" :class="buttonStyle(d.value)"
73
- :disabled="allowedDates && !allowedDates.includes(d.value)"
73
+ :disabled="(allowedDates && !allowedDates.includes(d.value)) || (onlyTodayAndFuture && isValidTodayAndFuture(d.value))"
74
74
  v-for="d in contextMenuDates" @click="setValue(d.value)">
75
75
  {{ d.date }}
76
76
  </button>
77
77
  </div>
78
+ <div class="mt-6">
79
+ <button type="button" class="text-primary" @click="this.$emit('update:modelValue', 'today')">Today</button>
80
+ </div>
78
81
  </div>
79
82
  </ContextMenu>
80
83
 
@@ -103,9 +106,9 @@ export default{
103
106
 
104
107
  setup(props, { emit }){
105
108
 
106
- const DD = ref(dayjs(props.modelValue ?? props.defaultValue).format('DD'))
107
- const MM = ref(dayjs(props.modelValue ?? props.defaultValue).format('MM'))
108
- const YYYY = ref(dayjs(props.modelValue ?? props.defaultValue).format('YYYY'))
109
+ const DD = ref(dayjs(props.mModelValue ?? props.defaultValue).format('DD'))
110
+ const MM = ref(dayjs(props.mModelValue ?? props.defaultValue).format('MM'))
111
+ const YYYY = ref(dayjs(props.mModelValue ?? props.defaultValue).format('YYYY'))
109
112
 
110
113
  watch([ YYYY, MM, DD ], (to) => {
111
114
  emit('update:modelValue', to.join('-'))
@@ -143,18 +146,30 @@ export default{
143
146
 
144
147
  allowedDates: Array,
145
148
 
149
+ onlyTodayAndFuture: Boolean
150
+
146
151
  },
147
152
 
148
153
  computed:{
149
154
 
155
+ mModelValue(){
156
+ if(this.modelValue === 'today')
157
+ return dayjs().format('YYYY-MM-DD')
158
+ return this.modelValue
159
+ },
160
+
150
161
  DMMMYYYY(){
151
- return this.modelValue ? dayjs(this.modelValue).format('D MMM YYYY') : ''
162
+ return this.mModelValue ? dayjs(this.mModelValue).format('D MMM YYYY') : ''
152
163
  },
153
164
 
154
165
  YYYYMMM(){
155
166
  return dayjs(this.contextMenu.value).format('YYYY-MM')
156
167
  },
157
168
 
169
+ today(){
170
+ return dayjs().format('YYYY-MM-DD')
171
+ },
172
+
158
173
  compClass(){
159
174
  return [
160
175
  this.$style.datepicker,
@@ -235,7 +250,7 @@ export default{
235
250
  buttonStyle(val){
236
251
  return [
237
252
  this.$style.button,
238
- this.modelValue === val ? this.$style.selected : '',
253
+ this.mModelValue === val ? this.$style.selected : '',
239
254
  dayjs(val).format('YYYY-MM') !== this.YYYYMMM ? this.$style.otherMonth : ''
240
255
  ]
241
256
  .join(' ')
@@ -255,6 +270,10 @@ export default{
255
270
 
256
271
  },
257
272
 
273
+ isValidTodayAndFuture(val){
274
+ return val.localeCompare(this.today) < 0
275
+ },
276
+
258
277
  setValue(d){
259
278
  this.$emit('update:modelValue', d)
260
279
  this.$emit('change', d)
@@ -21,7 +21,7 @@
21
21
  <button class="p-3" type="button" ref="listBtn" @click="$refs.listContext.open($refs.listBtn)">
22
22
  <svg width="14" height="14" class="fill-text-400 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M64 144c26.5 0 48-21.5 48-48s-21.5-48-48-48S16 69.5 16 96s21.5 48 48 48zM192 64c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zM64 464c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm48-208c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48z"/></svg>
23
23
  </button>
24
- <button v-if="uploadImage" class="p-3" type="button" @click="createImage">
24
+ <button v-if="canUpload" class="p-3" type="button" @click="createImage">
25
25
  <svg width="14" height="14" class="fill-text-400 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M152 120c-26.51 0-48 21.49-48 48s21.49 48 48 48s48-21.49 48-48S178.5 120 152 120zM447.1 32h-384C28.65 32-.0091 60.65-.0091 96v320c0 35.35 28.65 64 63.1 64h384c35.35 0 64-28.65 64-64V96C511.1 60.65 483.3 32 447.1 32zM463.1 409.3l-136.8-185.9C323.8 218.8 318.1 216 312 216c-6.113 0-11.82 2.768-15.21 7.379l-106.6 144.1l-37.09-46.1c-3.441-4.279-8.934-6.809-14.77-6.809c-5.842 0-11.33 2.529-14.78 6.809l-75.52 93.81c0-.0293 0 .0293 0 0L47.99 96c0-8.822 7.178-16 16-16h384c8.822 0 16 7.178 16 16V409.3z"/></svg>
26
26
  </button>
27
27
  <button class="p-3" type="button" @click="createLink">
@@ -35,7 +35,7 @@
35
35
  </div>
36
36
 
37
37
  <article ref="article" contenteditable="true" v-html="html" @paste="onPaste" spellcheck="false"
38
- @input="onInput" @click="onClick" :class="containerClass"
38
+ @input="onInput" @click="onClick" :class="itemClass"
39
39
  @blur="onBlur">
40
40
  </article>
41
41
 
@@ -137,10 +137,10 @@
137
137
  </template>
138
138
  <div class="flex-1 flex flex-col gap-5 p-5">
139
139
  <div>
140
- <Image class="min-h-[100px] bg-base-300 rounded-xl" ref="image" :src="newImage.src" editable="true"
140
+ <Image class="min-h-[100px] bg-text-50 cursor-pointer rounded-xl" ref="image" :src="newImage.src" editable="true"
141
141
  @click="$refs.image.edit()" @change="onImageChanged">
142
142
  <template #empty="{ instance }">
143
- <div class="absolute right-0 top-0 bottom-0 left-0 flex items-center justify-center">
143
+ <div class="absolute text-primary underline right-0 top-0 bottom-0 left-0 flex items-center justify-center">
144
144
  Click to Add Image...
145
145
  </div>
146
146
  </template>
@@ -259,12 +259,17 @@ export default{
259
259
  props:{
260
260
  modelValue: String,
261
261
  uploadImage: String,
262
+ uploadImageFn: String,
262
263
  containerClass: String,
263
264
  itemClass: String,
264
265
  },
265
266
 
266
267
  computed: {
267
268
 
269
+ canUpload(){
270
+ return `${this.uploadImage ?? ''}`.length > 0 || typeof this.uploadImageFn === 'function'
271
+ },
272
+
268
273
  linkCanSave(){
269
274
  return this.newLink.href && this.newLink.text
270
275
  },
@@ -310,8 +315,8 @@ export default{
310
315
  }
311
316
  else{
312
317
  this.newImage = {
313
- width: "",
314
- height: "",
318
+ width: "120px",
319
+ height: "120px",
315
320
  element: null
316
321
  }
317
322
  }
@@ -799,6 +799,12 @@ export default{
799
799
  }
800
800
  },
801
801
 
802
+ onConnect(reconnect){
803
+ if(reconnect){
804
+ this.load()
805
+ }
806
+ },
807
+
802
808
  onKeyDown(e){
803
809
 
804
810
  if(e.altKey){
@@ -850,6 +856,8 @@ export default{
850
856
  pop: this.loadQueued,
851
857
  delay: this.updateInterval
852
858
  })
859
+
860
+ this.socket.on('connect', this.onConnect)
853
861
  },
854
862
 
855
863
  unmounted() {
@@ -860,6 +868,8 @@ export default{
860
868
  this.socket.send('user.unsubscribe', {name: this.subscribeKey})
861
869
  this.socket.off(this.subscribeKey, this.onSignal)
862
870
  }
871
+
872
+ this.socket.off('connect', this.onConnect)
863
873
  },
864
874
 
865
875
  computed: {
@@ -2,86 +2,148 @@
2
2
  <div :class="$style.comp">
3
3
 
4
4
  <div :class="$style.header" v-if="visibleColumns.length > 0">
5
- <table :class="$style.table" ref="tableHead" :style="tableHeadStyle">
6
- <thead>
7
- <tr>
8
- <th v-for="column in visibleColumns" :style="thStyle(column)" :class="thClass(column)"
9
- v-tooltip="column.tooltip">
10
- <slot v-if="$slots['col-' + column.key]" :name="'col-' + column.key" :column="column"></slot>
11
- <div v-else :class="headerColumnClass(column)">
12
- <span>{{ column.label2 ? column.label2 : (column.label ?? column.key) }}</span>
13
- </div>
14
- <div :class="$style.separator" @mousedown="startResize($event, column)"></div>
15
- </th>
16
- <th :class="$style.spacer"></th>
17
- </tr>
18
- </thead>
19
- </table>
5
+ <table :class="$style.table">
6
+ <thead>
7
+ <tr>
8
+ <th v-for="column in freezeLeftColumns" :style="thStyle(column)" :class="thClass(column)"
9
+ v-tooltip="column.tooltip">
10
+ <slot v-if="$slots['col-' + column.key]" :name="'col-' + column.key" :column="column"></slot>
11
+ <div v-else :class="headerColumnClass(column)">
12
+ <span>{{ column.label2 ? column.label2 : (column.label ?? column.key) }}</span>
13
+ </div>
14
+ <div :class="$style.separator" @mousedown="startResize($event, column)"></div>
15
+ </th>
16
+ <th :class="$style.spacer"></th>
17
+ </tr>
18
+ </thead>
19
+ </table>
20
+ <div class="overflow-hidden">
21
+ <table :class="$style.table" ref="tableHead" :style="tableHeadStyle">
22
+ <thead>
23
+ <tr>
24
+ <th v-for="column in unfreezedColumns" :style="thStyle(column)" :class="thClass(column)"
25
+ v-tooltip="column.tooltip">
26
+ <slot v-if="$slots['col-' + column.key]" :name="'col-' + column.key" :column="column"></slot>
27
+ <div v-else :class="headerColumnClass(column)">
28
+ <span>{{ column.label2 ? column.label2 : (column.label ?? column.key) }}</span>
29
+ </div>
30
+ <div :class="$style.separator" @mousedown="startResize($event, column)"></div>
31
+ </th>
32
+ <th :class="$style.spacer"></th>
33
+ </tr>
34
+ </thead>
35
+ </table>
36
+ </div>
20
37
  </div>
21
38
 
22
- <div ref="cont">
23
- <div v-if="state === 3" ref="spinner" class="flex-1 flex items-center justify-center p-8">
24
- <svg class="animate-spin aspect-square w-[32px] h-[32px] text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
25
- </div>
26
- <div ref="scroller" v-else-if="visibleColumns.length > 0" :class="$style.scroller" :style="scrollerStyle">
27
- <div :class="$style.spacer" ref="spacer" :style="spacerStyle">
28
- <table :class="$style.table">
29
- <thead>
30
- <tr>
31
- <th v-for="column in visibleColumns" :style="thStyle(column)"></th>
32
- <th :class="$style.spacer"></th>
33
- </tr>
34
- </thead>
35
- <tbody ref="tbody">
39
+ <div ref="cont" class="flex-1 overflow-y-auto">
40
+ <div class="flex-1 flex flex-row">
41
+
42
+ <div :style="freezeLeftScrollerStyle">
43
+ <div :class="$style.spacer" :style="spacerStyle">
44
+ <table :class="$style.table" class="flex-1 overflow-x-auto">
45
+ <thead>
46
+ <tr>
47
+ <th v-for="column in freezeLeftColumns" :style="thStyle(column)"></th>
48
+ <th :class="$style.spacer"></th>
49
+ </tr>
50
+ </thead>
51
+ <tbody ref="tbody">
36
52
  <tr v-for="(item, index) in visibleItems" :key="item"
37
53
  @click="select(item, index)"
38
54
  :class="trClass(item, index)">
39
- <td v-for="(column, columnIndex) in visibleColumns"
55
+ <td v-for="(column, columnIndex) in freezeLeftColumns"
40
56
  :class="tdClass(item, column)"
41
57
  @click="$emit('item-click', item, column)">
42
58
 
43
59
  <div v-if="columnIndex === 0 && item._type === 'totalRow'" :class="$style.total">Total</div>
44
60
  <div v-else-if="item._type === 'totalRow' && !column.key.startsWith('_')"></div>
45
61
 
46
- <slot v-else-if="$slots[column.key]" :name="column.key" :column="column" :item="item" :index="visibleStartIndex + index"></slot>
47
- <slot v-else-if="$slots.default" name="default" :column="column" :item="item" :index="visibleStartIndex + index"></slot>
62
+ <slot v-else-if="$slots[column.key]" :name="column.key" :column="column" :item="item" :index="visibleStartIndex + index">
63
+ <div :class="columnClass(column, item)">&nbsp;</div>
64
+ </slot>
65
+ <slot v-else-if="$slots.default" name="default" :column="column" :item="item" :index="visibleStartIndex + index">
66
+ <div :class="columnClass(column, item)">&nbsp;</div>
67
+ </slot>
48
68
  <div v-else :class="columnClass(column, item)" v-html="formatColumn(item, column)"></div>
49
69
 
50
70
  </td>
51
71
  <td :class="$style.spacer"></td>
52
72
  </tr>
53
- </tbody>
54
- </table>
55
- <div v-if="state === 2" ref="spinner" class="h-[44px] relative">
56
- <div class="absolute top-0 left-0 w-screen h-[44px] flex items-center justify-center">
57
- <svg class="animate-spin aspect-square w-[16px] h-[16px] text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
58
- </div>
59
- </div>
60
- </div>
61
- </div>
62
- <div v-else-if="visibleColumns.length <= 0 && Array.isArray(columns)" class="text-center p-3 flex-1 min-h-[100%] flex items-center justify-center">
63
- <h5 class="text-text-300">No active column</h5>
64
- </div>
65
- <div v-else-if="Array.isArray(cItems) && cItems.length <= 0" class="text-center p-3 flex-1 min-h-[100%] flex items-center justify-center">
66
- <h5 class="text-text-300">No data available</h5>
67
- </div>
68
-
69
- <slot name="end"></slot>
70
-
71
- <div :class="$style.calc" v-if="visibleColumns.length > 0 && cItems && cItems.length > 0" ref="calc">
72
- <table :class="$style.table">
73
- <tbody>
74
- <tr>
75
- <td v-for="column in cColumns" :style="thStyle(column)" :class="thClass(column)">
76
- <slot v-if="$slots[column.key]" :name="column.key" :column="column" :item="cItems[0]"></slot>
77
- <slot v-else-if="$slots.default" name="default" :column="column" :item="cItems[0]"></slot>
78
- <div v-else :class="columnClass(column)" v-html="formatColumn(cItems[0] ?? {}, column)"></div>
79
- </td>
80
- <td :class="$style.spacer"></td>
81
- </tr>
82
- </tbody>
83
- </table>
84
- </div>
73
+ </tbody>
74
+ </table>
75
+ </div>
76
+ </div>
77
+
78
+ <div ref="xcont" class="flex-1 overflow-x-auto overflow-y-hidden">
79
+ <div v-if="state === 3" ref="spinner" class="flex-1 flex items-center justify-center p-8">
80
+ <svg class="animate-spin aspect-square w-[32px] h-[32px] text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
81
+ </div>
82
+ <div ref="scroller" v-else-if="visibleColumns.length > 0" :class="$style.scroller" :style="scrollerStyle">
83
+ <div :class="$style.spacer" ref="spacer" :style="spacerStyle">
84
+
85
+ <table :class="$style.table" class="flex-1 overflow-x-auto">
86
+ <thead>
87
+ <tr>
88
+ <th v-for="column in unfreezedColumns" :style="thStyle(column)"></th>
89
+ <th :class="$style.spacer"></th>
90
+ </tr>
91
+ </thead>
92
+ <tbody ref="tbody">
93
+ <tr v-for="(item, index) in visibleItems" :key="item"
94
+ @click="select(item, index)"
95
+ :class="trClass(item, index)">
96
+ <td v-for="(column, columnIndex) in unfreezedColumns"
97
+ :class="tdClass(item, column)"
98
+ @click="$emit('item-click', item, column)">
99
+
100
+ <div v-if="columnIndex === 0 && item._type === 'totalRow'" :class="$style.total">Total</div>
101
+ <div v-else-if="item._type === 'totalRow' && !column.key.startsWith('_')"></div>
102
+
103
+ <slot v-else-if="$slots[column.key]" :name="column.key" :column="column" :item="item" :index="visibleStartIndex + index">
104
+ <div :class="columnClass(column, item)">&nbsp;</div>
105
+ </slot>
106
+ <slot v-else-if="$slots.default" name="default" :column="column" :item="item" :index="visibleStartIndex + index">
107
+ <div :class="columnClass(column, item)">&nbsp;</div>
108
+ </slot>
109
+ <div v-else :class="columnClass(column, item)" v-html="formatColumn(item, column)"></div>
110
+
111
+ </td>
112
+ <td :class="$style.spacer"></td>
113
+ </tr>
114
+ </tbody>
115
+ </table>
116
+ <div v-if="state === 2" ref="spinner" class="h-[44px] relative">
117
+ <div class="absolute top-0 left-0 w-screen h-[44px] flex items-center justify-center">
118
+ <svg class="animate-spin aspect-square w-[16px] h-[16px] text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ <div v-else-if="visibleColumns.length <= 0 && Array.isArray(columns)" class="text-center p-3 flex-1 min-h-[100%] flex items-center justify-center">
124
+ <h5 class="text-text-300">No active column</h5>
125
+ </div>
126
+ <div v-else-if="Array.isArray(cItems) && cItems.length <= 0" class="text-center p-3 flex-1 min-h-[100%] flex items-center justify-center">
127
+ <h5 class="text-text-300">No data available</h5>
128
+ </div>
129
+ <slot name="end"></slot>
130
+ <div :class="$style.calc" v-if="visibleColumns.length > 0 && cItems && cItems.length > 0" ref="calc">
131
+ <table :class="$style.table">
132
+ <tbody>
133
+ <tr>
134
+ <td v-for="column in cColumns" :style="thStyle(column)" :class="thClass(column)">
135
+ <slot v-if="$slots[column.key]" :name="column.key" :column="column" :item="cItems[0]"></slot>
136
+ <slot v-else-if="$slots.default" name="default" :column="column" :item="cItems[0]"></slot>
137
+ <div v-else :class="columnClass(column)" v-html="formatColumn(cItems[0] ?? {}, column)"></div>
138
+ </td>
139
+ <td :class="$style.spacer"></td>
140
+ </tr>
141
+ </tbody>
142
+ </table>
143
+ </div>
144
+ </div>
145
+
146
+ </div>
85
147
  </div>
86
148
  </div>
87
149
  </template>
@@ -101,10 +163,15 @@ export default{
101
163
  emits: [ 'scroll-end', 'item-click' ],
102
164
 
103
165
  props:{
166
+
104
167
  columns: Array,
105
168
 
106
169
  enumCache: Object,
107
170
 
171
+ freezeLeft: Number,
172
+
173
+ freezeRight: Number,
174
+
108
175
  itemClass: String,
109
176
 
110
177
  uid: String,
@@ -130,6 +197,7 @@ export default{
130
197
  type: String,
131
198
 
132
199
  value: Object,
200
+
133
201
  },
134
202
 
135
203
  data(){
@@ -152,6 +220,11 @@ export default{
152
220
  return this.datasourceColumns ?? this.columns
153
221
  },
154
222
 
223
+ xContStyle(){
224
+ return {
225
+ }
226
+ },
227
+
155
228
  cItems(){
156
229
  return this.value?.items ?? this.items
157
230
  },
@@ -197,7 +270,7 @@ export default{
197
270
  return {}
198
271
 
199
272
  const height = (this.cItems.length * this.itemHeight) + (this.state === 2 ? 48 : 0)
200
- const width = this.visibleColumns.reduce((r, item) => r + parseInt(item.width ?? _DEFAULT_COLUMN_WIDTH), 0)
273
+ const width = this.unfreezedColumns.reduce((r, item) => r + parseInt(item.width ?? _DEFAULT_COLUMN_WIDTH), 0)
201
274
 
202
275
  return {
203
276
  height: height + 'px',
@@ -205,18 +278,57 @@ export default{
205
278
  }
206
279
  },
207
280
 
281
+ freezeLeftScrollerStyle(){
282
+ if(!this.cItems || this.cItems.length < 1)
283
+ return {}
284
+
285
+ const height = (this.cItems.length * this.itemHeight) + (this.state === 2 ? 48 : 0)
286
+ const width = this.freezeLeftColumns.reduce((r, item) => r + parseInt(item.width ?? _DEFAULT_COLUMN_WIDTH), 0)
287
+
288
+ return {
289
+ height: height + 'px',
290
+ width: width + 'px'
291
+ }
292
+ },
293
+
208
294
  tableHeadStyle(){
209
295
  return {
210
296
  transform: "translate3d(" + (this.scrollLeft * -1) + "px, 0, 0)"
211
297
  }
212
298
  },
213
299
 
300
+ freezeLeftColumns(){
301
+ const columns = []
302
+ for(let i = 0 ; i < (this.freezeLeft ?? 0) && i < this.visibleColumns.length ; i++){
303
+ const column = this.visibleColumns[i]
304
+ if(!('visible' in column) || column.visible){
305
+ columns.push(column)
306
+ }
307
+ }
308
+ return columns
309
+ },
310
+
311
+ unfreezedColumns(){
312
+ const columns = []
313
+ for(let i = this.freezeLeft ?? 0 ; i < this.visibleColumns.length ; i++){
314
+ const column = this.visibleColumns[i]
315
+ if(!('visible' in column) || column.visible){
316
+ columns.push(column)
317
+ }
318
+ }
319
+ return columns
320
+ },
321
+
214
322
  visibleColumns(){
215
- return (this.cColumns ?? []).filter(_ => !('visible' in _) || _.visible)
216
- },
323
+ const columns = []
324
+ for(let i = 0 ; i < this.cColumns.length ; i++){
325
+ const column = this.cColumns[i]
217
326
 
218
- visibleColumnKeys(){
219
- return this.visibleColumns.map(_ => _.key)
327
+ if(!('visible' in column) || column.visible){
328
+ columns.push(column)
329
+ }
330
+ }
331
+ return columns
220
332
  },
221
333
 
222
334
  },
@@ -229,6 +341,14 @@ export default{
229
341
  this.passiveScrollSupported() ? { passive: true } : false
230
342
  )
231
343
 
344
+ this.$refs.xcont.addEventListener(
345
+ "scroll",
346
+ this.handleScrollX,
347
+ this.passiveScrollSupported() ? { passive: true } : false
348
+ )
349
+
350
+ this.$refs.xcont.style.minHeight = `${this.$refs.cont.clientHeight}px`
351
+
232
352
  this.resize()
233
353
 
234
354
  const observer = new MutationObserver((mutationsList) => {
@@ -302,11 +422,18 @@ export default{
302
422
 
303
423
  },
304
424
 
425
+ handleScrollX: throttle(function(){
426
+ if(!this.$refs.scroller || !this.$refs.xcont) return
427
+
428
+ this.scrollLeft = this.$refs.xcont.scrollLeft
429
+
430
+ this.scrolled = this.scrollTop > 0
431
+ }, 16),
432
+
305
433
  handleScroll: throttle(function(){
306
434
  if(!this.$refs.scroller || !this.$refs.cont) return
307
435
 
308
436
  this.scrollTop = this.$refs.cont.scrollTop
309
- this.scrollLeft = this.$refs.cont.scrollLeft
310
437
 
311
438
  if(this.scrollTop > this.$refs.scroller.offsetHeight - this.$refs.cont.clientHeight - this.itemHeight){
312
439
  if(!this.isOnEndScroll){
@@ -625,7 +752,7 @@ export default{
625
752
  id: (this.items ?? [])[this.visibleStartIndex]?.id,
626
753
  index: this.visibleStartIndex
627
754
  }
628
- },
755
+ }
629
756
 
630
757
  }
631
758
 
@@ -641,11 +768,12 @@ export default{
641
768
  }
642
769
 
643
770
  .comp>*:last-child{
644
- @apply flex-1 overflow-auto relative max-h-[100vh];
771
+ @apply flex-1 relative max-h-[100vh];
645
772
  }
646
773
 
647
774
  .header{
648
775
  @apply border-b-[1px] border-text-50;
776
+ @apply flex flex-row;
649
777
  }
650
778
 
651
779
  .headerCol{
@@ -660,6 +788,7 @@ export default{
660
788
  }
661
789
 
662
790
  .spacer{
791
+ @apply w-full flex flex-row;
663
792
  will-change: auto;
664
793
  position: relative;
665
794
  height: 0;
@@ -711,10 +840,6 @@ export default{
711
840
  @apply border-text-50;
712
841
  }
713
842
 
714
- .spacer{
715
- @apply w-full
716
- }
717
-
718
843
  .align-left{ @apply text-left; }
719
844
  .align-center{ @apply text-center; }
720
845
  .align-right{ @apply text-right; }
@@ -276,6 +276,12 @@ const plugin = Plugin(function({ addBase, addUtilities, config, theme }) {
276
276
  'background-color': 'var(--panel-500)',
277
277
  },
278
278
 
279
+ '.text-ellipsis-nowrap': {
280
+ 'white-space': 'nowrap',
281
+ 'overflow': 'hidden',
282
+ 'text-overflow': 'ellipsis',
283
+ },
284
+
279
285
  })
280
286
 
281
287
  }, {
@@ -1085,6 +1085,14 @@ const pivotToSequelizeWhere = async (pivot, opt) => {
1085
1085
  sortKeyToOrders[`_${value.key}-${value.aggregrate}`] = literal(`COUNT(DISTINCT ${model.name}.${valueKey})`)
1086
1086
  break
1087
1087
 
1088
+ case 'first':
1089
+ attributes.push([ literal(`(SELECT ${valueKey} FROM ${model.tableName} WHERE id = MIN(${model.name}.id))`), `_${value.key}-${value.aggregrate}` ])
1090
+ break
1091
+
1092
+ case 'last':
1093
+ attributes.push([ literal(`(SELECT ${valueKey} FROM ${model.tableName} WHERE id = MAX(${model.name}.id))`), `_${value.key}-${value.aggregrate}` ])
1094
+ break
1095
+
1088
1096
  case 'count':
1089
1097
  attributes.push([ fn('COUNT', literal(`${model.name}.${valueKey}`)), `_${value.key}-${value.aggregrate}` ])
1090
1098
  sortKeyToOrders[`_${value.key}-${value.aggregrate}`] = fn('COUNT', literal(`${model.name}.${valueKey}`))
@@ -1254,6 +1262,8 @@ const filtersToSequelizeInclude = async(filters, opt, includes) => {
1254
1262
  include.where = { [Op.and]: [] }
1255
1263
  }
1256
1264
  include.where[Op.and].push({ [currentKey]: whereObj[key] })
1265
+
1266
+ include.required = true
1257
1267
  }
1258
1268
 
1259
1269
  //console.log('includes', util.inspect(includes, false, null, true /* enable colors */))
package/src/utils/wss.mjs CHANGED
@@ -46,6 +46,7 @@ class WSS extends EventEmitter2{
46
46
  _callbacks = {}
47
47
  _pendingSend = []
48
48
  _pinging = false
49
+ _retryCount = 1
49
50
 
50
51
  constructor(opt) {
51
52
  super();
@@ -143,15 +144,15 @@ class WSS extends EventEmitter2{
143
144
 
144
145
  };
145
146
 
146
- this._instance.onerror = (e) => {
147
- console.log('onerror', e)
147
+ this._instance.onopen = () => {
148
+ this._retryCount = 0
149
+ }
148
150
 
151
+ this._instance.onerror = (e) => {
149
152
  this.emit('error', e, [])
150
153
  }
151
154
 
152
155
  this._instance.onclose = (e) => {
153
- console.log('onclose', e)
154
-
155
156
  switch(e.code){
156
157
 
157
158
  case 1002:
@@ -163,9 +164,13 @@ class WSS extends EventEmitter2{
163
164
 
164
165
  default:
165
166
  this.emit('disconnect', null, [])
167
+
168
+ const retryAfter = (num => num > 60000 ? 60000 : num)(import.meta.env.DEV ? 1000 : Math.pow(1.2, ++this._retryCount) * 1000)
169
+ window.setTimeout(() => this.connect(true), retryAfter)
166
170
  break
167
171
  }
168
172
  };
173
+
169
174
  }
170
175
 
171
176
  async close(){
@@ -76,6 +76,8 @@
76
76
  <option value="">Default</option>
77
77
  <option value="count">Count</option>
78
78
  <option value="countDistinct">Distinct Count</option>
79
+ <option value="first">First</option>
80
+ <option value="last">Last</option>
79
81
  <option v-if="[ 'number', 'currency' ].includes(item.type)" value="sum">Sum</option>
80
82
  <option v-if="[ 'number', 'currency' ].includes(item.type)" value="avg">Average</option>
81
83
  <option v-if="[ 'number', 'currency' ].includes(item.type)" value="min">Min</option>