@mixd-id/web-scaffold 0.1.230406394 → 0.1.230406395

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.230406395",
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,41 +2,57 @@
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
 
@@ -50,38 +66,76 @@
50
66
  </td>
51
67
  <td :class="$style.spacer"></td>
52
68
  </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>
69
+ </tbody>
70
+ </table>
71
+ </div>
72
+ </div>
73
+
74
+ <div ref="xcont" class="flex-1 overflow-x-auto overflow-y-hidden">
75
+ <div v-if="state === 3" ref="spinner" class="flex-1 flex items-center justify-center p-8">
76
+ <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>
77
+ </div>
78
+ <div ref="scroller" v-else-if="visibleColumns.length > 0" :class="$style.scroller" :style="scrollerStyle">
79
+ <div :class="$style.spacer" ref="spacer" :style="spacerStyle">
80
+
81
+ <table :class="$style.table" class="flex-1 overflow-x-auto">
82
+ <thead>
83
+ <tr>
84
+ <th v-for="column in unfreezedColumns" :style="thStyle(column)"></th>
85
+ <th :class="$style.spacer"></th>
86
+ </tr>
87
+ </thead>
88
+ <tbody ref="tbody">
89
+ <tr v-for="(item, index) in visibleItems" :key="item"
90
+ @click="select(item, index)"
91
+ :class="trClass(item, index)">
92
+ <td v-for="(column, columnIndex) in unfreezedColumns"
93
+ :class="tdClass(item, column)"
94
+ @click="$emit('item-click', item, column)">
95
+
96
+ <div v-if="columnIndex === 0 && item._type === 'totalRow'" :class="$style.total">Total</div>
97
+ <div v-else-if="item._type === 'totalRow' && !column.key.startsWith('_')"></div>
98
+
99
+ <slot v-else-if="$slots[column.key]" :name="column.key" :column="column" :item="item" :index="visibleStartIndex + index"></slot>
100
+ <slot v-else-if="$slots.default" name="default" :column="column" :item="item" :index="visibleStartIndex + index"></slot>
101
+ <div v-else :class="columnClass(column, item)" v-html="formatColumn(item, column)"></div>
102
+
103
+ </td>
104
+ <td :class="$style.spacer"></td>
105
+ </tr>
106
+ </tbody>
107
+ </table>
108
+ <div v-if="state === 2" ref="spinner" class="h-[44px] relative">
109
+ <div class="absolute top-0 left-0 w-screen h-[44px] flex items-center justify-center">
110
+ <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>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ <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">
116
+ <h5 class="text-text-300">No active column</h5>
117
+ </div>
118
+ <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">
119
+ <h5 class="text-text-300">No data available</h5>
120
+ </div>
121
+ <slot name="end"></slot>
122
+ <div :class="$style.calc" v-if="visibleColumns.length > 0 && cItems && cItems.length > 0" ref="calc">
123
+ <table :class="$style.table">
124
+ <tbody>
125
+ <tr>
126
+ <td v-for="column in cColumns" :style="thStyle(column)" :class="thClass(column)">
127
+ <slot v-if="$slots[column.key]" :name="column.key" :column="column" :item="cItems[0]"></slot>
128
+ <slot v-else-if="$slots.default" name="default" :column="column" :item="cItems[0]"></slot>
129
+ <div v-else :class="columnClass(column)" v-html="formatColumn(cItems[0] ?? {}, column)"></div>
130
+ </td>
131
+ <td :class="$style.spacer"></td>
132
+ </tr>
133
+ </tbody>
134
+ </table>
135
+ </div>
136
+ </div>
137
+
138
+ </div>
85
139
  </div>
86
140
  </div>
87
141
  </template>
@@ -101,10 +155,15 @@ export default{
101
155
  emits: [ 'scroll-end', 'item-click' ],
102
156
 
103
157
  props:{
158
+
104
159
  columns: Array,
105
160
 
106
161
  enumCache: Object,
107
162
 
163
+ freezeLeft: Number,
164
+
165
+ freezeRight: Number,
166
+
108
167
  itemClass: String,
109
168
 
110
169
  uid: String,
@@ -130,6 +189,7 @@ export default{
130
189
  type: String,
131
190
 
132
191
  value: Object,
192
+
133
193
  },
134
194
 
135
195
  data(){
@@ -152,6 +212,11 @@ export default{
152
212
  return this.datasourceColumns ?? this.columns
153
213
  },
154
214
 
215
+ xContStyle(){
216
+ return {
217
+ }
218
+ },
219
+
155
220
  cItems(){
156
221
  return this.value?.items ?? this.items
157
222
  },
@@ -197,7 +262,7 @@ export default{
197
262
  return {}
198
263
 
199
264
  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)
265
+ const width = this.unfreezedColumns.reduce((r, item) => r + parseInt(item.width ?? _DEFAULT_COLUMN_WIDTH), 0)
201
266
 
202
267
  return {
203
268
  height: height + 'px',
@@ -205,18 +270,57 @@ export default{
205
270
  }
206
271
  },
207
272
 
273
+ freezeLeftScrollerStyle(){
274
+ if(!this.cItems || this.cItems.length < 1)
275
+ return {}
276
+
277
+ const height = (this.cItems.length * this.itemHeight) + (this.state === 2 ? 48 : 0)
278
+ const width = this.freezeLeftColumns.reduce((r, item) => r + parseInt(item.width ?? _DEFAULT_COLUMN_WIDTH), 0)
279
+
280
+ return {
281
+ height: height + 'px',
282
+ width: width + 'px'
283
+ }
284
+ },
285
+
208
286
  tableHeadStyle(){
209
287
  return {
210
288
  transform: "translate3d(" + (this.scrollLeft * -1) + "px, 0, 0)"
211
289
  }
212
290
  },
213
291
 
292
+ freezeLeftColumns(){
293
+ const columns = []
294
+ for(let i = 0 ; i < (this.freezeLeft ?? 0) && i < this.visibleColumns.length ; i++){
295
+ const column = this.visibleColumns[i]
296
+ if(!('visible' in column) || column.visible){
297
+ columns.push(column)
298
+ }
299
+ }
300
+ return columns
301
+ },
302
+
303
+ unfreezedColumns(){
304
+ const columns = []
305
+ for(let i = this.freezeLeft ?? 0 ; i < this.visibleColumns.length ; i++){
306
+ const column = this.visibleColumns[i]
307
+ if(!('visible' in column) || column.visible){
308
+ columns.push(column)
309
+ }
310
+ }
311
+ return columns
312
+ },
313
+
214
314
  visibleColumns(){
215
- return (this.cColumns ?? []).filter(_ => !('visible' in _) || _.visible)
216
- },
315
+ const columns = []
316
+ for(let i = 0 ; i < this.cColumns.length ; i++){
317
+ const column = this.cColumns[i]
217
318
 
218
- visibleColumnKeys(){
219
- return this.visibleColumns.map(_ => _.key)
319
+ if(!('visible' in column) || column.visible){
320
+ columns.push(column)
321
+ }
322
+ }
323
+ return columns
220
324
  },
221
325
 
222
326
  },
@@ -229,6 +333,14 @@ export default{
229
333
  this.passiveScrollSupported() ? { passive: true } : false
230
334
  )
231
335
 
336
+ this.$refs.xcont.addEventListener(
337
+ "scroll",
338
+ this.handleScrollX,
339
+ this.passiveScrollSupported() ? { passive: true } : false
340
+ )
341
+
342
+ this.$refs.xcont.style.minHeight = `${this.$refs.cont.clientHeight}px`
343
+
232
344
  this.resize()
233
345
 
234
346
  const observer = new MutationObserver((mutationsList) => {
@@ -302,11 +414,18 @@ export default{
302
414
 
303
415
  },
304
416
 
417
+ handleScrollX: throttle(function(){
418
+ if(!this.$refs.scroller || !this.$refs.xcont) return
419
+
420
+ this.scrollLeft = this.$refs.xcont.scrollLeft
421
+
422
+ this.scrolled = this.scrollTop > 0
423
+ }, 16),
424
+
305
425
  handleScroll: throttle(function(){
306
426
  if(!this.$refs.scroller || !this.$refs.cont) return
307
427
 
308
428
  this.scrollTop = this.$refs.cont.scrollTop
309
- this.scrollLeft = this.$refs.cont.scrollLeft
310
429
 
311
430
  if(this.scrollTop > this.$refs.scroller.offsetHeight - this.$refs.cont.clientHeight - this.itemHeight){
312
431
  if(!this.isOnEndScroll){
@@ -625,7 +744,7 @@ export default{
625
744
  id: (this.items ?? [])[this.visibleStartIndex]?.id,
626
745
  index: this.visibleStartIndex
627
746
  }
628
- },
747
+ }
629
748
 
630
749
  }
631
750
 
@@ -641,11 +760,12 @@ export default{
641
760
  }
642
761
 
643
762
  .comp>*:last-child{
644
- @apply flex-1 overflow-auto relative max-h-[100vh];
763
+ @apply flex-1 relative max-h-[100vh];
645
764
  }
646
765
 
647
766
  .header{
648
767
  @apply border-b-[1px] border-text-50;
768
+ @apply flex flex-row;
649
769
  }
650
770
 
651
771
  .headerCol{
@@ -660,6 +780,7 @@ export default{
660
780
  }
661
781
 
662
782
  .spacer{
783
+ @apply w-full flex flex-row;
663
784
  will-change: auto;
664
785
  position: relative;
665
786
  height: 0;
@@ -711,10 +832,6 @@ export default{
711
832
  @apply border-text-50;
712
833
  }
713
834
 
714
- .spacer{
715
- @apply w-full
716
- }
717
-
718
835
  .align-left{ @apply text-left; }
719
836
  .align-center{ @apply text-center; }
720
837
  .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.5, ++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>