@globalbrain/sefirot 4.34.1 → 4.35.1
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/config/nuxt.js +44 -1
- package/config/vite.js +2 -3
- package/lib/blocks/lens/FieldContext.ts +5 -0
- package/lib/blocks/lens/FieldData.ts +140 -0
- package/lib/blocks/lens/FieldRegistry.ts +23 -0
- package/lib/blocks/lens/FileDownloader.ts +1 -0
- package/lib/blocks/lens/FilterOperator.ts +33 -0
- package/lib/blocks/lens/LensQuery.ts +10 -0
- package/lib/blocks/lens/LensResult.ts +20 -0
- package/lib/blocks/lens/ResourceFetcher.ts +3 -0
- package/lib/blocks/lens/Rule.ts +12 -0
- package/lib/blocks/lens/components/LensCatalog.vue +490 -0
- package/lib/blocks/lens/components/LensCatalogControl.vue +220 -0
- package/lib/blocks/lens/components/LensCatalogFooter.vue +46 -0
- package/lib/blocks/lens/components/LensCatalogStateFilter.vue +171 -0
- package/lib/blocks/lens/components/LensCatalogStateFilterCondition.vue +86 -0
- package/lib/blocks/lens/components/LensCatalogStateFilterGroup.vue +102 -0
- package/lib/blocks/lens/components/LensCatalogStateSort.vue +159 -0
- package/lib/blocks/lens/components/LensFormFilter.vue +169 -0
- package/lib/blocks/lens/components/LensFormFilterCondition.vue +205 -0
- package/lib/blocks/lens/components/LensFormFilterGroup.vue +175 -0
- package/lib/blocks/lens/components/LensFormOverride.vue +45 -0
- package/lib/blocks/lens/components/LensFormOverrideBase.vue +204 -0
- package/lib/blocks/lens/components/LensFormView.vue +347 -0
- package/lib/blocks/lens/components/LensTable.vue +154 -0
- package/lib/blocks/lens/composables/FieldFactory.ts +27 -0
- package/lib/blocks/lens/composables/FieldRegistry.ts +16 -0
- package/lib/blocks/lens/composables/FileDownloader.ts +10 -0
- package/lib/blocks/lens/composables/ResourceFetcher.ts +30 -0
- package/lib/blocks/lens/composables/SetupLens.ts +55 -0
- package/lib/blocks/lens/fields/ContentField.ts +34 -0
- package/lib/blocks/lens/fields/DateField.ts +66 -0
- package/lib/blocks/lens/fields/DatetimeField.ts +35 -0
- package/lib/blocks/lens/fields/Field.ts +244 -0
- package/lib/blocks/lens/fields/FileUploadField.ts +63 -0
- package/lib/blocks/lens/fields/IdField.ts +34 -0
- package/lib/blocks/lens/fields/LinkField.ts +53 -0
- package/lib/blocks/lens/fields/NumberField.ts +32 -0
- package/lib/blocks/lens/fields/RelatedManyField.ts +62 -0
- package/lib/blocks/lens/fields/SelectField.ts +198 -0
- package/lib/blocks/lens/fields/SlackMessageField.ts +34 -0
- package/lib/blocks/lens/fields/TextField.ts +46 -0
- package/lib/blocks/lens/fields/TextareaField.ts +49 -0
- package/lib/blocks/lens/filter-inputs/FilterInput.ts +72 -0
- package/lib/blocks/lens/filter-inputs/NumberFilterInput.ts +26 -0
- package/lib/blocks/lens/filter-inputs/SelectFilterInput.ts +76 -0
- package/lib/blocks/lens/filter-inputs/TextFilterInput.ts +26 -0
- package/lib/blocks/lens/validation/RuleMapper.ts +22 -0
- package/lib/components/SInputTextarea.vue +28 -10
- package/lib/components/STable.vue +230 -61
- package/lib/components/STableCell.vue +2 -2
- package/lib/composables/TableAnimation.ts +180 -0
- package/lib/support/Scroll.ts +263 -0
- package/lib/support/Utils.ts +1 -1
- package/package.json +7 -15
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type TableCell } from '../../../composables/Table'
|
|
2
|
+
import { type NumberFieldData } from '../FieldData'
|
|
3
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
4
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
5
|
+
import { NumberFilterInput } from '../filter-inputs/NumberFilterInput'
|
|
6
|
+
import { Field } from './Field'
|
|
7
|
+
|
|
8
|
+
export class NumberField extends Field<NumberFieldData> {
|
|
9
|
+
override tableCell(v: any, _r: any): TableCell {
|
|
10
|
+
return {
|
|
11
|
+
type: 'number',
|
|
12
|
+
value: v
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
17
|
+
const number = new NumberFilterInput()
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
'=': number,
|
|
21
|
+
'!=': number
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
override dataListItemComponent(): any {
|
|
26
|
+
throw new Error('Not implemented.')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override formInputComponent() {
|
|
30
|
+
throw new Error('Not implemented.')
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type TableCell } from '../../../composables/Table'
|
|
2
|
+
import { type RelatedManyFieldData } from '../FieldData'
|
|
3
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
4
|
+
import { type ResourceFetcher } from '../ResourceFetcher'
|
|
5
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
6
|
+
import { SelectFilterInput } from '../filter-inputs/SelectFilterInput'
|
|
7
|
+
import { Field } from './Field'
|
|
8
|
+
|
|
9
|
+
export class RelatedManyField extends Field<RelatedManyFieldData> {
|
|
10
|
+
fetcher: ResourceFetcher
|
|
11
|
+
|
|
12
|
+
constructor(ctx: any, data: RelatedManyFieldData, fetcher: ResourceFetcher) {
|
|
13
|
+
super(ctx, data)
|
|
14
|
+
this.fetcher = fetcher
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override tableCell(v: any, _r: any): TableCell {
|
|
18
|
+
return {
|
|
19
|
+
type: 'pills',
|
|
20
|
+
pills: v.map((item: any) => ({
|
|
21
|
+
label: item[this.data.title],
|
|
22
|
+
value: item[this.data.filterKey]
|
|
23
|
+
}))
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
28
|
+
const method = this.data.resourceEndpointMethod
|
|
29
|
+
const url = this.data.resourceEndpointPath
|
|
30
|
+
const key = this.data.resourceEndpointDataKey
|
|
31
|
+
|
|
32
|
+
if (!url) {
|
|
33
|
+
return {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const optionsResolver = async () => {
|
|
37
|
+
const res = await this.fetcher(method, url)
|
|
38
|
+
const data = key ? res[key] : res
|
|
39
|
+
return data.map((item: any) => ({
|
|
40
|
+
value: item[this.data.filterKey],
|
|
41
|
+
label: item[this.data.resourceTitle]
|
|
42
|
+
}))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const selectOne = new SelectFilterInput().options(optionsResolver)
|
|
46
|
+
const selectMany = new SelectFilterInput().options(optionsResolver).multiple()
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
'=': selectOne,
|
|
50
|
+
'!=': selectOne,
|
|
51
|
+
'in': selectMany
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override dataListItemComponent(): any {
|
|
56
|
+
throw new Error('Not implemented.')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override formInputComponent() {
|
|
60
|
+
throw new Error('Not implemented.')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { xor } from 'lodash-es'
|
|
2
|
+
import { h } from 'vue'
|
|
3
|
+
import SInputCheckboxes from '../../../components/SInputCheckboxes.vue'
|
|
4
|
+
import SInputDropdown from '../../../components/SInputDropdown.vue'
|
|
5
|
+
import SInputRadios from '../../../components/SInputRadios.vue'
|
|
6
|
+
import SState from '../../../components/SState.vue'
|
|
7
|
+
import { type DropdownSection } from '../../../composables/Dropdown'
|
|
8
|
+
import { type TableCell } from '../../../composables/Table'
|
|
9
|
+
import { type SelectFieldData, type SelectFieldDataOption } from '../FieldData'
|
|
10
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
11
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
12
|
+
import { SelectFilterInput } from '../filter-inputs/SelectFilterInput'
|
|
13
|
+
import { Field } from './Field'
|
|
14
|
+
|
|
15
|
+
export class SelectField extends Field<SelectFieldData> {
|
|
16
|
+
override async tableFilterMenu(filters: any[], onFilterUpdated: (filters: any[]) => void): Promise<DropdownSection | null> {
|
|
17
|
+
const selected = this.inFilterValueFor(this.data.key, filters)
|
|
18
|
+
|
|
19
|
+
const options = this.data.options.map((o) => ({
|
|
20
|
+
label: this.labelForOption(o),
|
|
21
|
+
value: o.value
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
type: 'filter',
|
|
26
|
+
search: false,
|
|
27
|
+
selected,
|
|
28
|
+
options,
|
|
29
|
+
onClick: (v) => { onFilterUpdated?.([this.data.key, 'in', xor(selected, [v])]) }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override tableCell(v: any, _r: any): TableCell {
|
|
34
|
+
if (v === null) {
|
|
35
|
+
return { type: 'text', value: null }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
switch (this.data.displayAs) {
|
|
39
|
+
case 'state':
|
|
40
|
+
return this.tableCellState(v, _r)
|
|
41
|
+
case 'text':
|
|
42
|
+
return this.tableCellText(v, _r)
|
|
43
|
+
default:
|
|
44
|
+
throw new Error(`Unknown displayAs type: ${this.data.displayAs}`)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected tableCellText(v: any, _r: any): TableCell {
|
|
49
|
+
v = Array.isArray(v) ? v : [v]
|
|
50
|
+
|
|
51
|
+
v = this.optionsForValues(v).map((o) => this.labelForOption(o)).join(', ')
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
type: 'text',
|
|
55
|
+
value: v
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected tableCellState(v: any, _r: any): TableCell {
|
|
60
|
+
if (this.data.multiple) {
|
|
61
|
+
throw new Error('Displaying select field as state with multiple option is not supported.')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const option = this.optionForValue(v)
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
type: 'state',
|
|
68
|
+
mode: option.mode,
|
|
69
|
+
label: this.labelForOption(option)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
override availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
74
|
+
const options = this.data.options.map((o) => ({
|
|
75
|
+
label: this.labelForOption(o),
|
|
76
|
+
value: o.value
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
const selectOne = new SelectFilterInput().options(options)
|
|
80
|
+
const selectMany = new SelectFilterInput().options(options).multiple()
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
'=': selectOne,
|
|
84
|
+
'!=': selectOne,
|
|
85
|
+
'in': selectMany
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
override dataListItemComponent(): any {
|
|
90
|
+
return this.defineDataListItemComponent((value) => {
|
|
91
|
+
if (value === null) {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
switch (this.data.displayAs) {
|
|
96
|
+
case 'state':
|
|
97
|
+
return this.renderDataListItemValueForState(value)
|
|
98
|
+
case 'text':
|
|
99
|
+
return this.renderDataListItemValueForText(value)
|
|
100
|
+
default:
|
|
101
|
+
throw new Error(`Unknown displayAs type: ${this.data.displayAs}`)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected renderDataListItemValueForState(value: any): any {
|
|
107
|
+
if (this.data.multiple) {
|
|
108
|
+
throw new Error('Displaying select field as state with multiple option is not supported.')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const option = this.optionForValue(value)
|
|
112
|
+
|
|
113
|
+
return h(SState, {
|
|
114
|
+
mode: option.mode,
|
|
115
|
+
label: this.labelForOption(option)
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
protected renderDataListItemValueForText(value: any): any {
|
|
120
|
+
if (this.data.multiple) {
|
|
121
|
+
value = Array.isArray(value) ? value : [value]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return this.optionsForValues(value).map((o) => this.labelForOption(o)).join(', ')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
override inputEmptyValue(): any {
|
|
128
|
+
return this.data.multiple ? [] : null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
override formInputComponent(): any {
|
|
132
|
+
switch (this.data.inputAs) {
|
|
133
|
+
case 'dropdown':
|
|
134
|
+
return this.defineDropdownInputComponent()
|
|
135
|
+
case 'radio':
|
|
136
|
+
return this.defineRadioInputComponent()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
defineDropdownInputComponent(): any {
|
|
141
|
+
return this.defineFormInputComponent((props, { emit }) => {
|
|
142
|
+
return () => h(SInputDropdown, {
|
|
143
|
+
'size': 'md',
|
|
144
|
+
'label': this.formInputLabel(),
|
|
145
|
+
'placeholder': this.placeholder() || undefined,
|
|
146
|
+
'help': this.help() || undefined,
|
|
147
|
+
'options': this.data.options.map((o) => ({
|
|
148
|
+
label: this.labelForOption(o),
|
|
149
|
+
value: o.value
|
|
150
|
+
})),
|
|
151
|
+
'modelValue': props.modelValue,
|
|
152
|
+
'validation': props.validation,
|
|
153
|
+
'onUpdate:modelValue': (value: any) => {
|
|
154
|
+
emit('update:modelValue', value)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
defineRadioInputComponent(): any {
|
|
161
|
+
return this.defineFormInputComponent((props, { emit }) => {
|
|
162
|
+
const Comp = this.data.multiple ? SInputCheckboxes : SInputRadios
|
|
163
|
+
|
|
164
|
+
return () => h(Comp as any, {
|
|
165
|
+
'size': 'md',
|
|
166
|
+
'label': this.formInputLabel(),
|
|
167
|
+
'help': this.help() || undefined,
|
|
168
|
+
'options': this.data.options.map((o) => ({
|
|
169
|
+
label: this.labelForOption(o),
|
|
170
|
+
value: o.value
|
|
171
|
+
})),
|
|
172
|
+
'modelValue': props.modelValue,
|
|
173
|
+
'validation': props.validation,
|
|
174
|
+
'onUpdate:modelValue': (value: any) => {
|
|
175
|
+
emit('update:modelValue', value)
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
protected labelForOption(option: SelectFieldDataOption): string {
|
|
182
|
+
return this.ctx.lang === 'ja' ? option.labelJa : option.labelEn
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
protected optionForValue(value: string): SelectFieldDataOption {
|
|
186
|
+
const option = this.data.options.find((o) => o.value === value)
|
|
187
|
+
|
|
188
|
+
if (!option) {
|
|
189
|
+
throw new Error(`Option with value "${value}" not found in select field options.`)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return option
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
protected optionsForValues(values: string[]): SelectFieldDataOption[] {
|
|
196
|
+
return this.data.options.filter((o) => values.includes(o.value))
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
import SLink from '../../../components/SLink.vue'
|
|
3
|
+
import { type TableCell } from '../../../composables/Table'
|
|
4
|
+
import { type SlackMessageFieldData } from '../FieldData'
|
|
5
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
6
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
7
|
+
import { Field } from './Field'
|
|
8
|
+
|
|
9
|
+
export class SlackMessageField extends Field<SlackMessageFieldData> {
|
|
10
|
+
override tableCell(v: any, _r: any): TableCell {
|
|
11
|
+
return {
|
|
12
|
+
type: 'text',
|
|
13
|
+
value: v,
|
|
14
|
+
link: v,
|
|
15
|
+
color: 'info'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
20
|
+
return {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override dataListItemComponent(): any {
|
|
24
|
+
return this.defineDataListItemComponent((value) => {
|
|
25
|
+
return h(SLink, { href: value }, () => value)
|
|
26
|
+
}, {
|
|
27
|
+
lineClamp: 1
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override formInputComponent(): any {
|
|
32
|
+
throw new Error('Not implemented.')
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
import SInputText from '../../../components/SInputText.vue'
|
|
3
|
+
import { type TableCell } from '../../../composables/Table'
|
|
4
|
+
import { type TextFieldData } from '../FieldData'
|
|
5
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
6
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
7
|
+
import { TextFilterInput } from '../filter-inputs/TextFilterInput'
|
|
8
|
+
import { Field } from './Field'
|
|
9
|
+
|
|
10
|
+
export class TextField extends Field<TextFieldData> {
|
|
11
|
+
override tableCell(v: any, _r: any): TableCell {
|
|
12
|
+
return {
|
|
13
|
+
type: 'text',
|
|
14
|
+
value: v
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
19
|
+
const text = new TextFilterInput()
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
'=': text,
|
|
23
|
+
'!=': text
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override dataListItemComponent(): any {
|
|
28
|
+
return this.defineDataListItemComponent((value) => value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override formInputComponent(): any {
|
|
32
|
+
return this.defineFormInputComponent((props, { emit }) => {
|
|
33
|
+
return () => h(SInputText, {
|
|
34
|
+
'size': 'md',
|
|
35
|
+
'label': this.formInputLabel(),
|
|
36
|
+
'placeholder': this.placeholder() || undefined,
|
|
37
|
+
'help': this.help() || undefined,
|
|
38
|
+
'modelValue': props.modelValue,
|
|
39
|
+
'validation': props.validation,
|
|
40
|
+
'onUpdate:modelValue': (value: any) => {
|
|
41
|
+
emit('update:modelValue', value)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
import SInputTextarea from '../../../components/SInputTextarea.vue'
|
|
3
|
+
import { type TableCell } from '../../../composables/Table'
|
|
4
|
+
import { type TextareaFieldData } from '../FieldData'
|
|
5
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
6
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
7
|
+
import { TextFilterInput } from '../filter-inputs/TextFilterInput'
|
|
8
|
+
import { Field } from './Field'
|
|
9
|
+
|
|
10
|
+
export class TextareaField extends Field<TextareaFieldData> {
|
|
11
|
+
override tableCell(v: any, _r: any): TableCell {
|
|
12
|
+
return {
|
|
13
|
+
type: 'text',
|
|
14
|
+
value: v
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
19
|
+
const text = new TextFilterInput()
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
'=': text,
|
|
23
|
+
'!=': text
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override dataListItemComponent(): any {
|
|
28
|
+
return this.defineDataListItemComponent((value) => value, {
|
|
29
|
+
preWrap: true
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override formInputComponent(): any {
|
|
34
|
+
return this.defineFormInputComponent((props, { emit }) => {
|
|
35
|
+
return () => h(SInputTextarea, {
|
|
36
|
+
'size': 'md',
|
|
37
|
+
'label': this.formInputLabel(),
|
|
38
|
+
'placeholder': this.placeholder() || undefined,
|
|
39
|
+
'help': this.help() || undefined,
|
|
40
|
+
'rows': this.data.rows,
|
|
41
|
+
'modelValue': props.modelValue,
|
|
42
|
+
'validation': props.validation,
|
|
43
|
+
'onUpdate:modelValue': (value: any) => {
|
|
44
|
+
emit('update:modelValue', value)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type ValidationArgs } from '@vuelidate/core'
|
|
2
|
+
import { defineComponent, h } from 'vue'
|
|
3
|
+
|
|
4
|
+
export abstract class FilterInput {
|
|
5
|
+
/**
|
|
6
|
+
* Returns the validation rules for the filter input.
|
|
7
|
+
*/
|
|
8
|
+
abstract rules(): Record<string, ValidationArgs>
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Casts the input value to the correct type.
|
|
12
|
+
*
|
|
13
|
+
* This method gets called during rendering filter conditions in
|
|
14
|
+
* `LensFormFilterCondition` when the operator changes.
|
|
15
|
+
*
|
|
16
|
+
* We have to cast the value to the correct type when the input changes.
|
|
17
|
+
*
|
|
18
|
+
* For example, when filtering select input, and at first the user setups
|
|
19
|
+
* single select value filter: ['status', '=', 'open'].
|
|
20
|
+
*
|
|
21
|
+
* Then, user changes the operator to multi select: ['status', 'in', 'open'].
|
|
22
|
+
* Now, the value must be converted to array: ['status', 'in', ['open']].
|
|
23
|
+
*/
|
|
24
|
+
abstract castValue(value: any): any
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Helper method to cast value to string.
|
|
28
|
+
*/
|
|
29
|
+
protected castValueToStringOrNull(value: any): string | null {
|
|
30
|
+
if (value == null) {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
return String(value)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Helper method to cast value to number.
|
|
38
|
+
*/
|
|
39
|
+
protected castValueToNumberOrNull(value: any): number | null {
|
|
40
|
+
if (value == null) {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
const n = Number(value)
|
|
44
|
+
if (Number.isNaN(n)) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
return n
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Converts the value to a text representation for display. This value gets
|
|
52
|
+
* displayed in the <LensCatalogStateFilterCondition> component.
|
|
53
|
+
*/
|
|
54
|
+
abstract valueToText(value: any): Promise<string>
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns the Vue component for the filter input.
|
|
58
|
+
*/
|
|
59
|
+
abstract component(): any
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Helper function to define a Vue component with props merging.
|
|
63
|
+
*/
|
|
64
|
+
protected defineComponent(component: any, props: any) {
|
|
65
|
+
return defineComponent((baseProps) => {
|
|
66
|
+
return () => h(component, {
|
|
67
|
+
...baseProps,
|
|
68
|
+
...props
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ValidationArgs } from '@vuelidate/core'
|
|
2
|
+
import SInputNumber from '../../../components/SInputNumber.vue'
|
|
3
|
+
import { required } from '../../../validation/rules'
|
|
4
|
+
import { FilterInput } from './FilterInput'
|
|
5
|
+
|
|
6
|
+
export class NumberFilterInput extends FilterInput {
|
|
7
|
+
rules(): Record<string, ValidationArgs> {
|
|
8
|
+
return {
|
|
9
|
+
required: required()
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
castValue(value: any): any {
|
|
14
|
+
return this.castValueToNumberOrNull(value)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async valueToText(value: any): Promise<string> {
|
|
18
|
+
return value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
component(): any {
|
|
22
|
+
return this.defineComponent(SInputNumber, {
|
|
23
|
+
size: 'sm'
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type ValidationArgs } from '@vuelidate/core'
|
|
2
|
+
import { defineAsyncComponent } from 'vue'
|
|
3
|
+
import SInputDropdown, { type Option } from '../../../components/SInputDropdown.vue'
|
|
4
|
+
import { required } from '../../../validation/rules'
|
|
5
|
+
import { FilterInput } from './FilterInput'
|
|
6
|
+
|
|
7
|
+
export class SelectFilterInput extends FilterInput {
|
|
8
|
+
protected _multiple = false
|
|
9
|
+
|
|
10
|
+
protected optionsResolver: Option[] | (() => Promise<Option[]>) = []
|
|
11
|
+
|
|
12
|
+
multiple(): this {
|
|
13
|
+
this._multiple = true
|
|
14
|
+
return this
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
options(resolver: Option[] | (() => Promise<Option[]>)): this {
|
|
18
|
+
this.optionsResolver = resolver
|
|
19
|
+
return this
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
rules(): Record<string, ValidationArgs> {
|
|
23
|
+
return {
|
|
24
|
+
required: required()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
castValue(value: any): any {
|
|
29
|
+
return this._multiple
|
|
30
|
+
? this.castValueMany(value)
|
|
31
|
+
: this.castValueOne(value)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
protected castValueOne(value: any): any {
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
return value[0]
|
|
37
|
+
}
|
|
38
|
+
return this.castValueToStringOrNull(value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected castValueMany(value: any): any {
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
return value
|
|
44
|
+
}
|
|
45
|
+
return [value]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async valueToText(value: any): Promise<string> {
|
|
49
|
+
const options = await this.resolveOptions()
|
|
50
|
+
|
|
51
|
+
return this.valueAsArray(value)
|
|
52
|
+
.map((v) => options.find((o) => o.value === v)!.label)
|
|
53
|
+
.join(', ')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected valueAsArray(value: any): any[] {
|
|
57
|
+
return Array.isArray(value) ? value : [value]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
component(): any {
|
|
61
|
+
return defineAsyncComponent(async () => {
|
|
62
|
+
const options = await this.resolveOptions()
|
|
63
|
+
return this.defineComponent(SInputDropdown, {
|
|
64
|
+
size: 'sm',
|
|
65
|
+
options
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected async resolveOptions(): Promise<Option[]> {
|
|
71
|
+
if (typeof this.optionsResolver === 'function') {
|
|
72
|
+
return await this.optionsResolver()
|
|
73
|
+
}
|
|
74
|
+
return this.optionsResolver
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ValidationArgs } from '@vuelidate/core'
|
|
2
|
+
import SInputText from '../../../components/SInputText.vue'
|
|
3
|
+
import { required } from '../../../validation/rules'
|
|
4
|
+
import { FilterInput } from './FilterInput'
|
|
5
|
+
|
|
6
|
+
export class TextFilterInput extends FilterInput {
|
|
7
|
+
rules(): Record<string, ValidationArgs> {
|
|
8
|
+
return {
|
|
9
|
+
required: required()
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
castValue(value: any): any {
|
|
14
|
+
return this.castValueToStringOrNull(value)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async valueToText(value: any): Promise<string> {
|
|
18
|
+
return value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
component(): any {
|
|
22
|
+
return this.defineComponent(SInputText, {
|
|
23
|
+
size: 'sm'
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ValidationArgs, type ValidationRuleWithParams } from '@vuelidate/core'
|
|
2
|
+
import { maxLength, required } from '../../../validation/rules'
|
|
3
|
+
import { type Rule } from '../Rule'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Maps field rules to vuelidate validation rules.
|
|
7
|
+
*/
|
|
8
|
+
export function map(rules: Rule[]): ValidationArgs {
|
|
9
|
+
return rules.reduce((carry: ValidationArgs<any>, rule: Rule) => {
|
|
10
|
+
carry[rule.type] = mapRule(rule)
|
|
11
|
+
return carry
|
|
12
|
+
}, {})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function mapRule(rule: Rule): ValidationRuleWithParams {
|
|
16
|
+
switch (rule.type) {
|
|
17
|
+
case 'max_length':
|
|
18
|
+
return maxLength(rule.length)
|
|
19
|
+
case 'required':
|
|
20
|
+
return required()
|
|
21
|
+
}
|
|
22
|
+
}
|