@d-mok/quasar-app-extension-quasar-axe 3.1.64 → 3.1.67
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
|
@@ -19,8 +19,11 @@ type PropType<C extends VueComponent> = Omit<
|
|
|
19
19
|
>
|
|
20
20
|
type OkType<C extends VueComponent> = Parameters<InstanceType<C>['$emit']>[1]
|
|
21
21
|
|
|
22
|
-
export async function
|
|
23
|
-
|
|
22
|
+
export async function custom<C extends VueComponent>(
|
|
23
|
+
component: C,
|
|
24
|
+
props?: PropType<C>
|
|
25
|
+
): Promise<OkType<C>> {
|
|
26
|
+
return new Promise<OkType<C>>((resolve, reject) =>
|
|
24
27
|
Dialog.create({
|
|
25
28
|
component,
|
|
26
29
|
componentProps: {
|
|
@@ -28,18 +31,11 @@ export async function base(component: Component, props: any) {
|
|
|
28
31
|
...props,
|
|
29
32
|
},
|
|
30
33
|
})
|
|
31
|
-
.onOk(
|
|
34
|
+
.onOk(resolve)
|
|
32
35
|
.onCancel(() => reject('dialog cancelled by user.'))
|
|
33
36
|
)
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
export async function custom<C extends VueComponent>(
|
|
37
|
-
component: C,
|
|
38
|
-
props?: PropType<C>
|
|
39
|
-
): Promise<OkType<C>> {
|
|
40
|
-
return await base(component, props ?? {})
|
|
41
|
-
}
|
|
42
|
-
|
|
43
39
|
/**
|
|
44
40
|
* Ask for a string by a text area. Allow empty.
|
|
45
41
|
*/
|
|
@@ -49,7 +45,7 @@ export async function askTextarea(
|
|
|
49
45
|
prefill: string = '',
|
|
50
46
|
isValid: Predicate<string> = $ => true
|
|
51
47
|
): Promise<string> {
|
|
52
|
-
return await
|
|
48
|
+
return await custom(dialogTextarea, {
|
|
53
49
|
title,
|
|
54
50
|
message,
|
|
55
51
|
prefill,
|
|
@@ -67,12 +63,11 @@ export async function askBtn<T>(
|
|
|
67
63
|
items: T[],
|
|
68
64
|
label?: (string & keyof T) | Mapper<T, string>
|
|
69
65
|
): Promise<T> {
|
|
70
|
-
return await
|
|
66
|
+
return await custom(dialogBtn, {
|
|
71
67
|
title,
|
|
72
68
|
message,
|
|
73
69
|
items,
|
|
74
70
|
labelFunc: ($: T) => Object.print($, label),
|
|
75
|
-
cancel: true,
|
|
76
71
|
})
|
|
77
72
|
}
|
|
78
73
|
|
|
@@ -94,7 +89,9 @@ export async function askFn(
|
|
|
94
89
|
* The first non-empty prefill will be used.
|
|
95
90
|
* The last non-empty prefill will be the spec.
|
|
96
91
|
*/
|
|
97
|
-
export async function askTable<
|
|
92
|
+
export async function askTable<
|
|
93
|
+
T extends Record<string, string | number | boolean>
|
|
94
|
+
>(
|
|
98
95
|
title: string,
|
|
99
96
|
message: string = '',
|
|
100
97
|
prefills: T[][],
|
|
@@ -102,43 +99,17 @@ export async function askTable<T extends object>(
|
|
|
102
99
|
arrayValidators: [(_: T[]) => boolean, string][] = [[$ => true, '']],
|
|
103
100
|
headersMap: Record<string, string> = {}
|
|
104
101
|
): Promise<T[]> {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return await
|
|
102
|
+
const ps = prefills.unmatch($ => $.length === 0)
|
|
103
|
+
if (ps.length === 0) throwError('Error', 'All prefills are empty array.')
|
|
104
|
+
return await custom(dialogTable, {
|
|
108
105
|
title,
|
|
109
106
|
message,
|
|
110
|
-
content:
|
|
111
|
-
sample:
|
|
112
|
-
validators,
|
|
113
|
-
arrayValidators,
|
|
107
|
+
content: ps[0]!,
|
|
108
|
+
sample: ps.at(-1)![0]!,
|
|
109
|
+
validators: validators as any,
|
|
110
|
+
arrayValidators: arrayValidators as any,
|
|
114
111
|
headersMap,
|
|
115
112
|
cancel: true,
|
|
116
|
-
allowAddRow: true,
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Edit an array of objects by table.
|
|
122
|
-
*/
|
|
123
|
-
export async function editTable<T extends object>(
|
|
124
|
-
title: string,
|
|
125
|
-
message: string = '',
|
|
126
|
-
prefill: T[],
|
|
127
|
-
validators: [(_: T) => boolean, string][] = [[$ => true, '']],
|
|
128
|
-
arrayValidators: [(_: T[]) => boolean, string][] = [[$ => true, '']],
|
|
129
|
-
headersMap: Record<string, string> = {}
|
|
130
|
-
): Promise<T[]> {
|
|
131
|
-
if (prefill.length === 0) throwError('Error', 'prefill is empty array.')
|
|
132
|
-
return await base(dialogTable, {
|
|
133
|
-
title,
|
|
134
|
-
message,
|
|
135
|
-
content: prefill,
|
|
136
|
-
sample: prefill[0],
|
|
137
|
-
validators,
|
|
138
|
-
arrayValidators,
|
|
139
|
-
headersMap,
|
|
140
|
-
cancel: true,
|
|
141
|
-
allowAddRow: false,
|
|
142
113
|
})
|
|
143
114
|
}
|
|
144
115
|
|
|
@@ -170,7 +141,7 @@ export async function showTextarea(
|
|
|
170
141
|
message: string = '',
|
|
171
142
|
content: string = ''
|
|
172
143
|
): Promise<void> {
|
|
173
|
-
await
|
|
144
|
+
await custom(dialogTextarea, {
|
|
174
145
|
title,
|
|
175
146
|
message,
|
|
176
147
|
prefill: content,
|
|
@@ -185,21 +156,20 @@ export async function showTextarea(
|
|
|
185
156
|
export async function showTable(
|
|
186
157
|
title: string,
|
|
187
158
|
message: string,
|
|
188
|
-
content:
|
|
159
|
+
content: Record<string, string | number | boolean>[]
|
|
189
160
|
): Promise<void> {
|
|
190
161
|
if (content.length === 0) {
|
|
191
162
|
content = [{ empty: '' }]
|
|
192
163
|
}
|
|
193
|
-
await
|
|
164
|
+
await custom(dialogTable, {
|
|
194
165
|
title,
|
|
195
166
|
message,
|
|
196
167
|
content,
|
|
197
|
-
sample: content[0]
|
|
168
|
+
sample: content[0]!,
|
|
198
169
|
validators: [[($: any) => true, '']],
|
|
199
170
|
arrayValidators: [[($: any) => true, '']],
|
|
200
171
|
headersMap: {},
|
|
201
172
|
cancel: false,
|
|
202
|
-
allowAddRow: false,
|
|
203
173
|
})
|
|
204
174
|
}
|
|
205
175
|
|
|
@@ -209,7 +179,7 @@ export async function showTable(
|
|
|
209
179
|
export async function showArray(
|
|
210
180
|
title: string,
|
|
211
181
|
message: string = '',
|
|
212
|
-
content:
|
|
182
|
+
content: (string | number)[] = []
|
|
213
183
|
): Promise<void> {
|
|
214
184
|
await showTable(
|
|
215
185
|
title,
|
|
@@ -225,7 +195,7 @@ export async function askFile(
|
|
|
225
195
|
title: string,
|
|
226
196
|
message: string = ''
|
|
227
197
|
): Promise<File> {
|
|
228
|
-
return await
|
|
198
|
+
return await custom(dialogFile, {
|
|
229
199
|
title,
|
|
230
200
|
message,
|
|
231
201
|
multiple: false,
|
|
@@ -240,7 +210,7 @@ export async function askFiles(
|
|
|
240
210
|
title: string,
|
|
241
211
|
message: string = ''
|
|
242
212
|
): Promise<File[]> {
|
|
243
|
-
return await
|
|
213
|
+
return await custom(dialogFile, {
|
|
244
214
|
title,
|
|
245
215
|
message,
|
|
246
216
|
multiple: true,
|
|
@@ -259,14 +229,13 @@ export async function askRadioText(
|
|
|
259
229
|
newPlaceholder: string = '',
|
|
260
230
|
isValid: Predicate<string> = $ => true
|
|
261
231
|
): Promise<string> {
|
|
262
|
-
return await
|
|
232
|
+
return await custom(dialogRadioText, {
|
|
263
233
|
title,
|
|
264
234
|
message,
|
|
265
235
|
items,
|
|
266
236
|
prefill,
|
|
267
237
|
newPlaceholder,
|
|
268
238
|
isValid,
|
|
269
|
-
cancel: true,
|
|
270
239
|
})
|
|
271
240
|
}
|
|
272
241
|
|
|
@@ -281,14 +250,13 @@ export async function askCheckboxText(
|
|
|
281
250
|
newPlaceholder: string = '',
|
|
282
251
|
isValid: Predicate<string[]> = $ => true
|
|
283
252
|
): Promise<string[]> {
|
|
284
|
-
return await
|
|
253
|
+
return await custom(dialogCheckboxText, {
|
|
285
254
|
title,
|
|
286
255
|
message,
|
|
287
256
|
items,
|
|
288
257
|
prefill,
|
|
289
258
|
newPlaceholder,
|
|
290
259
|
isValid,
|
|
291
|
-
cancel: true,
|
|
292
260
|
})
|
|
293
261
|
}
|
|
294
262
|
|
|
@@ -360,11 +328,15 @@ export async function askForm<T extends FormPrefill>(
|
|
|
360
328
|
return $
|
|
361
329
|
})
|
|
362
330
|
|
|
363
|
-
|
|
331
|
+
const output = await custom(dialogForm, {
|
|
364
332
|
title,
|
|
365
333
|
message,
|
|
366
|
-
prefill: standardPrefill,
|
|
367
|
-
validators,
|
|
368
|
-
onChange,
|
|
334
|
+
prefill: standardPrefill as any,
|
|
335
|
+
validators: validators as any,
|
|
336
|
+
onChange: onChange as any,
|
|
369
337
|
})
|
|
338
|
+
|
|
339
|
+
return Object.mapValues(output, v =>
|
|
340
|
+
typeof v === 'string' ? v.trim() : v
|
|
341
|
+
) as any
|
|
370
342
|
}
|
|
@@ -24,16 +24,14 @@
|
|
|
24
24
|
<handson
|
|
25
25
|
v-model="dummy"
|
|
26
26
|
:sample="sample"
|
|
27
|
-
:allowAddRow="allowAddRow"
|
|
28
27
|
:headersMap="headersMap"
|
|
29
28
|
v-if="exist"
|
|
30
29
|
></handson>
|
|
31
30
|
</q-card>
|
|
32
31
|
<div
|
|
33
|
-
v-
|
|
32
|
+
v-if="err"
|
|
34
33
|
class="text-red"
|
|
35
|
-
|
|
36
|
-
v-html="err()"
|
|
34
|
+
v-html="err.join('<br/>')"
|
|
37
35
|
/>
|
|
38
36
|
</q-card-section>
|
|
39
37
|
|
|
@@ -53,7 +51,7 @@
|
|
|
53
51
|
label="OK"
|
|
54
52
|
flat
|
|
55
53
|
@click="onDialogOK(filterBlank(dummy))"
|
|
56
|
-
:disable="cancel && err
|
|
54
|
+
:disable="cancel && err !== false"
|
|
57
55
|
/>
|
|
58
56
|
</q-card-actions>
|
|
59
57
|
</q-card>
|
|
@@ -63,7 +61,7 @@
|
|
|
63
61
|
<script lang="ts" setup>
|
|
64
62
|
import handson from './handson.vue'
|
|
65
63
|
import { useDialogPluginComponent } from 'quasar'
|
|
66
|
-
import { ref } from 'vue'
|
|
64
|
+
import { computed, ref } from 'vue'
|
|
67
65
|
import { clone } from './schema'
|
|
68
66
|
import { copyToClipboard } from 'quasar'
|
|
69
67
|
import * as v from 'valibot'
|
|
@@ -83,7 +81,6 @@ const {
|
|
|
83
81
|
validators,
|
|
84
82
|
arrayValidators,
|
|
85
83
|
cancel,
|
|
86
|
-
allowAddRow,
|
|
87
84
|
headersMap,
|
|
88
85
|
} = defineProps<{
|
|
89
86
|
title: string
|
|
@@ -93,7 +90,6 @@ const {
|
|
|
93
90
|
validators: [(_: row) => boolean, string][]
|
|
94
91
|
arrayValidators: [(_: row[]) => boolean, string][]
|
|
95
92
|
cancel: boolean
|
|
96
|
-
allowAddRow: boolean
|
|
97
93
|
headersMap: Record<string, string>
|
|
98
94
|
}>()
|
|
99
95
|
|
|
@@ -125,40 +121,39 @@ const EmptySchema = v.strictObject(
|
|
|
125
121
|
})
|
|
126
122
|
)
|
|
127
123
|
|
|
128
|
-
|
|
124
|
+
const err = computed(() => {
|
|
129
125
|
let rows = dummy.value
|
|
130
126
|
|
|
131
|
-
for (let i =
|
|
132
|
-
let row = rows[i]!
|
|
127
|
+
for (let i = 1; i <= rows.length; i++) {
|
|
128
|
+
let row = rows[i - 1]!
|
|
133
129
|
if (v.is(EmptySchema, row)) continue
|
|
134
130
|
|
|
135
131
|
if (!v.is(StandardSchema, row)) {
|
|
136
132
|
if (Object.keys(row).length > Object.keys(sample).length) {
|
|
137
|
-
return
|
|
133
|
+
return [`#${i} has extra column`]
|
|
138
134
|
}
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
Object.entries(sample)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
135
|
+
return [
|
|
136
|
+
`#${i} require format:`,
|
|
137
|
+
...Object.entries(sample).map(
|
|
138
|
+
([k, v]) => `${headersMap[k] ?? k}: ${v}`
|
|
139
|
+
),
|
|
140
|
+
]
|
|
145
141
|
}
|
|
146
142
|
|
|
147
143
|
for (let [f, msg] of validators) {
|
|
148
|
-
if (!f(row)) return
|
|
144
|
+
if (!f(row)) return [`#${i}:`, msg]
|
|
149
145
|
}
|
|
150
146
|
}
|
|
151
147
|
|
|
152
148
|
for (let [f, msg] of arrayValidators) {
|
|
153
149
|
if (!f(filterBlank(rows))) {
|
|
154
|
-
return msg
|
|
150
|
+
return [msg]
|
|
155
151
|
}
|
|
156
152
|
}
|
|
157
153
|
return false
|
|
158
|
-
}
|
|
154
|
+
})
|
|
159
155
|
|
|
160
156
|
function filterBlank(rows: row[]): row[] {
|
|
161
|
-
if (!allowAddRow) return rows
|
|
162
157
|
return rows.unmatch(r => v.is(EmptySchema, r))
|
|
163
158
|
}
|
|
164
159
|
|
|
@@ -172,7 +167,7 @@ function copy() {
|
|
|
172
167
|
const TOTAL_WIDTH =
|
|
173
168
|
200 +
|
|
174
169
|
Object.values(
|
|
175
|
-
[...content, Object.mapValues(sample, (v, k) => k)]
|
|
170
|
+
[...content, Object.mapValues(sample, (v, k) => headersMap[k] ?? k)]
|
|
176
171
|
.map(r => Object.mapValues(r, v => v.toString().length))
|
|
177
172
|
.reduce((acc, obj) => {
|
|
178
173
|
for (const key in acc) {
|
|
@@ -7,16 +7,24 @@
|
|
|
7
7
|
width="100%"
|
|
8
8
|
stretchH="all"
|
|
9
9
|
:data="content"
|
|
10
|
+
:dataSchema="
|
|
11
|
+
Object.mapValues(sample, val =>
|
|
12
|
+
typeof val === 'boolean'
|
|
13
|
+
? false
|
|
14
|
+
: typeof val === 'number'
|
|
15
|
+
? NaN
|
|
16
|
+
: ''
|
|
17
|
+
)
|
|
18
|
+
"
|
|
10
19
|
:colHeaders="Object.keys(sample).map($ => headersMap[$] ?? $)"
|
|
11
20
|
:columns="columns()"
|
|
12
21
|
:rowHeaders="true"
|
|
22
|
+
:rowHeaderWidth="50"
|
|
13
23
|
:afterChange="shake"
|
|
14
|
-
:afterRemoveRow="afterRemoveRow"
|
|
15
24
|
:afterCreateRow="shake"
|
|
25
|
+
:afterRemoveRow="shake"
|
|
16
26
|
:afterCopy="afterCopy"
|
|
17
|
-
:contextMenu="
|
|
18
|
-
allowAddRow ? ['row_above', 'row_below', 'remove_row'] : []
|
|
19
|
-
"
|
|
27
|
+
:contextMenu="['row_above', 'row_below', 'remove_row']"
|
|
20
28
|
/>
|
|
21
29
|
</template>
|
|
22
30
|
|
|
@@ -24,7 +32,9 @@
|
|
|
24
32
|
import { ref } from 'vue'
|
|
25
33
|
import { HotTable } from '@handsontable/vue3'
|
|
26
34
|
import { registerAllModules } from 'handsontable/registry'
|
|
27
|
-
import
|
|
35
|
+
import Handsontable from 'handsontable'
|
|
36
|
+
|
|
37
|
+
type row = Record<string, string | number | boolean>
|
|
28
38
|
|
|
29
39
|
registerAllModules()
|
|
30
40
|
|
|
@@ -32,20 +42,45 @@ registerAllModules()
|
|
|
32
42
|
|
|
33
43
|
let hotTableComponent = ref<InstanceType<typeof HotTable> | null>(null)
|
|
34
44
|
|
|
35
|
-
function
|
|
45
|
+
function hot(): Handsontable {
|
|
36
46
|
return hotTableComponent.value.hotInstance
|
|
37
47
|
}
|
|
38
48
|
|
|
39
|
-
const content = defineModel<
|
|
40
|
-
required: true,
|
|
41
|
-
})
|
|
49
|
+
const content = defineModel<row[]>({ required: true })
|
|
42
50
|
|
|
43
|
-
const { sample,
|
|
44
|
-
sample:
|
|
45
|
-
allowAddRow: boolean
|
|
51
|
+
const { sample, headersMap } = defineProps<{
|
|
52
|
+
sample: row
|
|
46
53
|
headersMap: Record<string, string>
|
|
47
54
|
}>()
|
|
48
55
|
|
|
56
|
+
function numericRenderer(
|
|
57
|
+
instance: Handsontable.Core,
|
|
58
|
+
td: HTMLTableCellElement,
|
|
59
|
+
row: number,
|
|
60
|
+
col: number,
|
|
61
|
+
prop: string | number,
|
|
62
|
+
value: any,
|
|
63
|
+
cellProperties: Handsontable.CellProperties
|
|
64
|
+
) {
|
|
65
|
+
Handsontable.renderers.NumericRenderer(
|
|
66
|
+
instance,
|
|
67
|
+
td,
|
|
68
|
+
row,
|
|
69
|
+
col,
|
|
70
|
+
prop,
|
|
71
|
+
value,
|
|
72
|
+
cellProperties
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const isInvalid = value === null || value === undefined || isNaN(value)
|
|
76
|
+
|
|
77
|
+
if (isInvalid) {
|
|
78
|
+
td.innerText = ''
|
|
79
|
+
const isBlankRow = instance.getDataAtRow(row).belongs(['', false, NaN])
|
|
80
|
+
if (!isBlankRow) td.style.backgroundColor = '#ffbeba'
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
49
84
|
function columns() {
|
|
50
85
|
return Object.entries(sample).map(([field, val]) => ({
|
|
51
86
|
data: field,
|
|
@@ -56,30 +91,29 @@ function columns() {
|
|
|
56
91
|
? 'numeric'
|
|
57
92
|
: 'text',
|
|
58
93
|
allowEmpty: false,
|
|
94
|
+
renderer: typeof val === 'number' ? numericRenderer : undefined,
|
|
59
95
|
}))
|
|
60
96
|
}
|
|
61
97
|
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
98
|
+
function shake(): void {
|
|
99
|
+
function coerceString(x: any): string {
|
|
100
|
+
if (x === null) return ''
|
|
101
|
+
if (x === undefined) return ''
|
|
102
|
+
return String(x).replaceAll(String.fromCodePoint(0x00a0), ' ').trim()
|
|
103
|
+
}
|
|
67
104
|
|
|
68
|
-
function coerceNumber(x: any): number {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
105
|
+
function coerceNumber(x: any): number {
|
|
106
|
+
if (typeof x !== 'number') return NaN
|
|
107
|
+
return Number(x)
|
|
108
|
+
}
|
|
72
109
|
|
|
73
|
-
function coerceBoolean(x: any): boolean {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return false
|
|
80
|
-
}
|
|
110
|
+
function coerceBoolean(x: any): boolean {
|
|
111
|
+
if (typeof x === 'boolean') return x
|
|
112
|
+
if (x === 'true') return true
|
|
113
|
+
if (x === 'TRUE') return true
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
81
116
|
|
|
82
|
-
function shake(): void {
|
|
83
117
|
const keys = Object.keys(sample)
|
|
84
118
|
for (let row of content.value) {
|
|
85
119
|
for (let [k, v] of Object.entries(row)) {
|
|
@@ -90,18 +124,15 @@ function shake(): void {
|
|
|
90
124
|
if (!keys.includes(k)) delete row[k]
|
|
91
125
|
}
|
|
92
126
|
}
|
|
93
|
-
}
|
|
94
127
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
128
|
+
const lastRow = content.value.at(-1)
|
|
129
|
+
|
|
130
|
+
if (!lastRow || !Object.values(lastRow).belongs(['', false, NaN])) {
|
|
131
|
+
const h = hot()
|
|
132
|
+
h.alter('insert_row_below', null as any, 1)
|
|
100
133
|
}
|
|
101
134
|
}
|
|
102
135
|
|
|
103
|
-
// aftercopy
|
|
104
|
-
|
|
105
136
|
async function afterCopy(data: (string | number | boolean)[][], coords: any) {
|
|
106
137
|
// fix pasting space as non break space problem
|
|
107
138
|
let text = data.map(r => r.join('\t')).join('\n')
|