@globalbrain/sefirot 4.34.0 → 4.35.0
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 +53 -2
- package/config/vite.js +43 -5
- 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,27 @@
|
|
|
1
|
+
import { useLang } from '../../../composables/Lang'
|
|
2
|
+
import { type FieldContext } from '../FieldContext'
|
|
3
|
+
import { type FieldData } from '../FieldData'
|
|
4
|
+
import { type Field } from '../fields/Field'
|
|
5
|
+
import { useFieldRegistry } from './FieldRegistry'
|
|
6
|
+
|
|
7
|
+
export interface FieldFactory {
|
|
8
|
+
make<T extends FieldData>(fieldData: T): Field<T>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useFieldFactory(): FieldFactory {
|
|
12
|
+
const lang = useLang()
|
|
13
|
+
const fieldRegistry = useFieldRegistry()
|
|
14
|
+
|
|
15
|
+
const ctx: FieldContext = {
|
|
16
|
+
lang
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function make<T extends FieldData>(fieldData: T): Field<T> {
|
|
20
|
+
const provider = fieldRegistry.resolve(fieldData)
|
|
21
|
+
return provider(ctx, fieldData)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
make
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type InjectionKey, inject, provide } from 'vue'
|
|
2
|
+
import { type FieldRegistry } from '../FieldRegistry'
|
|
3
|
+
|
|
4
|
+
export const FieldRegistryKey: InjectionKey<FieldRegistry> = Symbol.for('lens-field-registry')
|
|
5
|
+
|
|
6
|
+
export function provideFieldRegistry(registry: FieldRegistry): void {
|
|
7
|
+
provide(FieldRegistryKey, registry)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useFieldRegistry(): FieldRegistry {
|
|
11
|
+
const registry = inject(FieldRegistryKey)
|
|
12
|
+
if (!registry) {
|
|
13
|
+
throw new Error('FieldRegistry is not provided. Make sure to call SetupLens.')
|
|
14
|
+
}
|
|
15
|
+
return registry
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useGet } from '../../../composables/Api'
|
|
2
|
+
import { type FileDownloader } from '../FileDownloader'
|
|
3
|
+
|
|
4
|
+
export function useFileDownloader(): FileDownloader {
|
|
5
|
+
const { execute: fileDownloader } = useGet<any, [url: string]>(async (http, url) => {
|
|
6
|
+
return http.download(url)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
return fileDownloader
|
|
10
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import { useGet } from '../../../composables/Api'
|
|
3
|
+
import { type ResourceFetchMethod, type ResourceFetcher } from '../ResourceFetcher'
|
|
4
|
+
|
|
5
|
+
export function useResourceFetcher(): ResourceFetcher {
|
|
6
|
+
const data = ref<Record<string, any>>({})
|
|
7
|
+
const pendingList: Record<string, Promise<any> | undefined> = {}
|
|
8
|
+
|
|
9
|
+
const { execute: resourceFetcher } = useGet<any, [method: ResourceFetchMethod, url: string]>(async (http, method, url) => {
|
|
10
|
+
const key = `${method}:${url}`
|
|
11
|
+
|
|
12
|
+
if (data.value[key]) {
|
|
13
|
+
return data.value[key]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (pendingList[key]) {
|
|
17
|
+
return pendingList[key]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pendingList[key] = http[method](url).then((response) => {
|
|
21
|
+
data.value[key] = response
|
|
22
|
+
delete pendingList[key]
|
|
23
|
+
return response
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return pendingList[key]
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return resourceFetcher
|
|
30
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type FieldDataType } from '../FieldData'
|
|
2
|
+
import { type FieldDataFor, type FieldProvider, FieldRegistry } from '../FieldRegistry'
|
|
3
|
+
import { provideFieldRegistry } from '../composables/FieldRegistry'
|
|
4
|
+
import { ContentField } from '../fields/ContentField'
|
|
5
|
+
import { DateField } from '../fields/DateField'
|
|
6
|
+
import { DatetimeField } from '../fields/DatetimeField'
|
|
7
|
+
import { FileUploadField } from '../fields/FileUploadField'
|
|
8
|
+
import { IdField } from '../fields/IdField'
|
|
9
|
+
import { LinkField } from '../fields/LinkField'
|
|
10
|
+
import { NumberField } from '../fields/NumberField'
|
|
11
|
+
import { RelatedManyField } from '../fields/RelatedManyField'
|
|
12
|
+
import { SelectField } from '../fields/SelectField'
|
|
13
|
+
import { SlackMessageField } from '../fields/SlackMessageField'
|
|
14
|
+
import { TextField } from '../fields/TextField'
|
|
15
|
+
import { TextareaField } from '../fields/TextareaField'
|
|
16
|
+
import { useFileDownloader } from './FileDownloader'
|
|
17
|
+
import { useResourceFetcher } from './ResourceFetcher'
|
|
18
|
+
|
|
19
|
+
export interface SetupLens {
|
|
20
|
+
registerField<T extends FieldDataType>(type: T, provider: FieldProvider<FieldDataFor<T>>): void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useSetupLens(): SetupLens {
|
|
24
|
+
const resourceFetcher = useResourceFetcher()
|
|
25
|
+
const fileDownloader = useFileDownloader()
|
|
26
|
+
|
|
27
|
+
const fieldRegistry = new FieldRegistry()
|
|
28
|
+
|
|
29
|
+
provideFieldRegistry(fieldRegistry)
|
|
30
|
+
|
|
31
|
+
registerDefaultFields()
|
|
32
|
+
|
|
33
|
+
function registerDefaultFields(): void {
|
|
34
|
+
fieldRegistry.register('content', (ctx, field) => new ContentField(ctx, field))
|
|
35
|
+
fieldRegistry.register('date', (ctx, field) => new DateField(ctx, field))
|
|
36
|
+
fieldRegistry.register('datetime', (ctx, field) => new DatetimeField(ctx, field))
|
|
37
|
+
fieldRegistry.register('file_upload', (ctx, field) => new FileUploadField(ctx, field, fileDownloader))
|
|
38
|
+
fieldRegistry.register('link', (ctx, field) => new LinkField(ctx, field))
|
|
39
|
+
fieldRegistry.register('number', (ctx, field) => new NumberField(ctx, field))
|
|
40
|
+
fieldRegistry.register('id', (ctx, field) => new IdField(ctx, field))
|
|
41
|
+
fieldRegistry.register('related_many', (ctx, field) => new RelatedManyField(ctx, field, resourceFetcher))
|
|
42
|
+
fieldRegistry.register('select', (ctx, field) => new SelectField(ctx, field))
|
|
43
|
+
fieldRegistry.register('slack_message', (ctx, field) => new SlackMessageField(ctx, field))
|
|
44
|
+
fieldRegistry.register('text', (ctx, field) => new TextField(ctx, field))
|
|
45
|
+
fieldRegistry.register('textarea', (ctx, field) => new TextareaField(ctx, field))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function registerField<T extends FieldDataType>(type: T, provider: FieldProvider<FieldDataFor<T>>): void {
|
|
49
|
+
fieldRegistry.register(type, provider)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
registerField
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
import SContent from '../../../components/SContent.vue'
|
|
3
|
+
import SMarkdown from '../../../components/SMarkdown.vue'
|
|
4
|
+
import { type TableCell } from '../../../composables/Table'
|
|
5
|
+
import { type ContentFieldData } from '../FieldData'
|
|
6
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
7
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
8
|
+
import { Field } from './Field'
|
|
9
|
+
|
|
10
|
+
export class ContentField extends Field<ContentFieldData> {
|
|
11
|
+
tableCell(_v: any, _r: any): TableCell {
|
|
12
|
+
return { type: 'empty' }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
16
|
+
return {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
dataListItemComponent(): any {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
formInputComponent(): any {
|
|
24
|
+
return this.defineFormInputComponent((_props, _ctx) => {
|
|
25
|
+
return () => h(SMarkdown, {
|
|
26
|
+
content: this.ctx.lang === 'ja' ? this.data.bodyJa : this.data.bodyEn
|
|
27
|
+
}, {
|
|
28
|
+
default: (slot: any) => {
|
|
29
|
+
return h(SContent, { innerHTML: slot.rendered })
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
import SInputDate from '../../../components/SInputDate.vue'
|
|
3
|
+
import { type TableCell } from '../../../composables/Table'
|
|
4
|
+
import { day } from '../../../support/Day'
|
|
5
|
+
import { type DateFieldData } from '../FieldData'
|
|
6
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
7
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
8
|
+
import { TextFilterInput } from '../filter-inputs/TextFilterInput'
|
|
9
|
+
import { Field } from './Field'
|
|
10
|
+
|
|
11
|
+
export class DateField extends Field<DateFieldData> {
|
|
12
|
+
tableCell(v: any, _r: any): TableCell {
|
|
13
|
+
return {
|
|
14
|
+
type: 'day',
|
|
15
|
+
value: v ? day(v) : null,
|
|
16
|
+
format: 'YYYY-MM-DD'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
21
|
+
const text = new TextFilterInput()
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
'=': text,
|
|
25
|
+
'!=': text
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
dataListItemComponent(): any {
|
|
30
|
+
return this.defineDataListItemComponent((value) => {
|
|
31
|
+
return value ? day(value).format('YYYY-MM-DD') : ''
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
inputToPayload(value: any): any {
|
|
36
|
+
if (value === null) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return value.format('YYYY-MM-DD')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
payloadToInput(value: any): any {
|
|
44
|
+
if (value === null) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return day(value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
formInputComponent(): any {
|
|
52
|
+
return this.defineFormInputComponent((props, { emit }) => {
|
|
53
|
+
return () => h(SInputDate, {
|
|
54
|
+
'size': 'md',
|
|
55
|
+
'label': this.formInputLabel(),
|
|
56
|
+
'placeholder': this.placeholder() || undefined,
|
|
57
|
+
'help': this.help() || undefined,
|
|
58
|
+
'modelValue': props.modelValue,
|
|
59
|
+
'validation': props.validation,
|
|
60
|
+
'onUpdate:modelValue': (value: any) => {
|
|
61
|
+
emit('update:modelValue', value)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type TableCell } from '../../../composables/Table'
|
|
2
|
+
import { day } from '../../../support/Day'
|
|
3
|
+
import { type DatetimeFieldData } from '../FieldData'
|
|
4
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
5
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
6
|
+
import { TextFilterInput } from '../filter-inputs/TextFilterInput'
|
|
7
|
+
import { Field } from './Field'
|
|
8
|
+
|
|
9
|
+
export class DatetimeField extends Field<DatetimeFieldData> {
|
|
10
|
+
tableCell(v: any, _r: any): TableCell {
|
|
11
|
+
return {
|
|
12
|
+
type: 'day',
|
|
13
|
+
value: v ? day(v) : null
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
18
|
+
const text = new TextFilterInput()
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
'=': text,
|
|
22
|
+
'!=': text
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
dataListItemComponent(): any {
|
|
27
|
+
return this.defineDataListItemComponent((value) => {
|
|
28
|
+
return value ? day(value).format('YYYY-MM-DD') : ''
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
formInputComponent(): any {
|
|
33
|
+
throw new Error('Not implemented.')
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { type ValidationArgs } from '@vuelidate/core'
|
|
2
|
+
import { defineComponent, h } from 'vue'
|
|
3
|
+
import SDataListItem from '../../../components/SDataListItem.vue'
|
|
4
|
+
import { type DropdownSection } from '../../../composables/Dropdown'
|
|
5
|
+
import { type TableCell, type TableColumn } from '../../../composables/Table'
|
|
6
|
+
import { type FieldContext } from '../FieldContext'
|
|
7
|
+
import { type FieldData } from '../FieldData'
|
|
8
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
9
|
+
import { type LensQuerySort } from '../LensQuery'
|
|
10
|
+
import LensFormOverrideBase from '../components/LensFormOverrideBase.vue'
|
|
11
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
12
|
+
import * as RuleMapper from '../validation/RuleMapper'
|
|
13
|
+
|
|
14
|
+
export abstract class Field<T extends FieldData> {
|
|
15
|
+
/**
|
|
16
|
+
* The field context, that holds global app context
|
|
17
|
+
* like the current language.
|
|
18
|
+
*/
|
|
19
|
+
protected ctx: FieldContext
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The field data returned from the backend.
|
|
23
|
+
*/
|
|
24
|
+
protected data: T
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new field instance.
|
|
28
|
+
*/
|
|
29
|
+
constructor(ctx: FieldContext, fieldData: T) {
|
|
30
|
+
this.ctx = ctx
|
|
31
|
+
this.data = fieldData
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns the label value for the field depending
|
|
36
|
+
* on the current app language.
|
|
37
|
+
*/
|
|
38
|
+
label(): string {
|
|
39
|
+
return this.ctx.lang === 'ja' ? this.data.labelJa : this.data.labelEn
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns the placeholder value for the field depending
|
|
44
|
+
* on the current app language.
|
|
45
|
+
*/
|
|
46
|
+
placeholder(): string | null {
|
|
47
|
+
return this.ctx.lang === 'ja'
|
|
48
|
+
? (this.data as any).placeholderJa ?? null
|
|
49
|
+
: (this.data as any).placeholderEn ?? null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns the help text value for the field depending
|
|
54
|
+
* on the current app language.
|
|
55
|
+
*/
|
|
56
|
+
help(): string | null {
|
|
57
|
+
return this.ctx.lang === 'ja'
|
|
58
|
+
? (this.data as any).helpJa ?? null
|
|
59
|
+
: (this.data as any).helpEn ?? null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Renders the table column configuration for the field.
|
|
64
|
+
*/
|
|
65
|
+
tableColumn(): TableColumn<any, any, any, any> {
|
|
66
|
+
return {
|
|
67
|
+
label: this.label(),
|
|
68
|
+
freeze: this.data.freeze,
|
|
69
|
+
width: `${this.data.width}px`,
|
|
70
|
+
cell: (v, r) => this.tableCell(v, r)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Renders the table sort menu for the field. The sort maybe null when
|
|
76
|
+
* the field `sortable` option is false.
|
|
77
|
+
*/
|
|
78
|
+
tableSortMenu(onSortUpdated: (sort: LensQuerySort) => void): DropdownSection | null {
|
|
79
|
+
if (!this.data.sortable) {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const trans = {
|
|
84
|
+
en: { sort_asc: 'Sort ascending (A...Z)', sort_desc: 'Sort descending (Z...A)' },
|
|
85
|
+
ja: { sort_asc: 'ソート: 昇順 (A...Z)', sort_desc: 'ソート: 降順 (Z...A)' }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const t = trans[this.ctx.lang]
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
type: 'menu',
|
|
92
|
+
options: [
|
|
93
|
+
{ label: t.sort_asc, onClick: () => onSortUpdated?.([this.data.key, 'asc']) },
|
|
94
|
+
{ label: t.sort_desc, onClick: () => onSortUpdated?.([this.data.key, 'desc']) }
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Renders the table filter menu for the field. The filter maybe null when
|
|
101
|
+
* the field has no available filters.
|
|
102
|
+
*/
|
|
103
|
+
async tableFilterMenu(_filters: any[], _onFilterUpdated: (filters: any[]) => void): Promise<DropdownSection | null> {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Returns the "in" filter values for the given key from the filters array.
|
|
109
|
+
*/
|
|
110
|
+
protected inFilterValueFor(key: string, filters: any[]): any[] {
|
|
111
|
+
for (const f of filters) {
|
|
112
|
+
if (f[0] !== key) {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
if (f[1] !== 'in' || !Array.isArray(f[2])) {
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
return f[2]
|
|
119
|
+
}
|
|
120
|
+
return []
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Returns the table cell definition for the field.
|
|
125
|
+
*/
|
|
126
|
+
abstract tableCell(v: any, r: any): TableCell
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Returns the available filter operators for the field.
|
|
130
|
+
*/
|
|
131
|
+
abstract availableFilters(): Partial<Record<FilterOperator, FilterInput>>
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Returns the available filter operators as an array of strings.
|
|
135
|
+
*/
|
|
136
|
+
availableFilterOperators(): FilterOperator[] {
|
|
137
|
+
return Object.keys(this.availableFilters()) as FilterOperator[]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Returns the filter input by operator.
|
|
142
|
+
*/
|
|
143
|
+
filterInputByOperator(operator: FilterOperator): FilterInput {
|
|
144
|
+
const filters = this.availableFilters()
|
|
145
|
+
if (!filters[operator]) {
|
|
146
|
+
throw new Error(`Filter operator "${operator}" is not available for this field.`)
|
|
147
|
+
}
|
|
148
|
+
return filters[operator]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns the override inputs for the field.
|
|
153
|
+
*/
|
|
154
|
+
overrideForm(): any {
|
|
155
|
+
return defineComponent((props) => {
|
|
156
|
+
return () => h(LensFormOverrideBase, {
|
|
157
|
+
name: props.name,
|
|
158
|
+
field: props.field,
|
|
159
|
+
labelEn: props.override.labelEn,
|
|
160
|
+
labelJa: props.override.labelJa,
|
|
161
|
+
width: props.override.width,
|
|
162
|
+
freeze: props.override.freeze
|
|
163
|
+
})
|
|
164
|
+
}, {
|
|
165
|
+
props: [
|
|
166
|
+
'name',
|
|
167
|
+
'field',
|
|
168
|
+
'override'
|
|
169
|
+
]
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Returns the data list item component for the field.
|
|
175
|
+
*/
|
|
176
|
+
abstract dataListItemComponent(): any
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Helper function to define the data list item component.
|
|
180
|
+
*/
|
|
181
|
+
protected defineDataListItemComponent(fn: (value: any) => any, options: any = null): any {
|
|
182
|
+
return defineComponent((props) => {
|
|
183
|
+
return () => h(SDataListItem, options, {
|
|
184
|
+
label: () => this.label(),
|
|
185
|
+
value: () => fn(props.value)
|
|
186
|
+
})
|
|
187
|
+
}, {
|
|
188
|
+
props: ['value']
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Returns the value should be used when the input is empty.
|
|
194
|
+
*/
|
|
195
|
+
inputEmptyValue(): any {
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Converts the form input value to the payload value.
|
|
201
|
+
*/
|
|
202
|
+
inputToPayload(value: any): any {
|
|
203
|
+
return value
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Converts the payload value to the form input value.
|
|
208
|
+
*/
|
|
209
|
+
payloadToInput(value: any): any {
|
|
210
|
+
return value
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Returns the form input component for the field.
|
|
215
|
+
*/
|
|
216
|
+
abstract formInputComponent(): any
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Helper function to define the form input component.
|
|
220
|
+
*/
|
|
221
|
+
protected defineFormInputComponent(fn: (props: any, emit: any) => any): any {
|
|
222
|
+
return defineComponent(fn, {
|
|
223
|
+
props: ['modelValue', 'validation'],
|
|
224
|
+
emits: ['update:modelValue']
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Generate the form input label based on required status.
|
|
230
|
+
*/
|
|
231
|
+
protected formInputLabel(): string {
|
|
232
|
+
return this.data.required ? `${this.label()} *` : this.label()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Generates the vuelidate validation rules for the field.
|
|
237
|
+
*/
|
|
238
|
+
generateValidationRules(): ValidationArgs {
|
|
239
|
+
return RuleMapper.map([
|
|
240
|
+
...(this.data.required ? [{ type: 'required' } as const] : []),
|
|
241
|
+
...this.data.rules
|
|
242
|
+
])
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
import SDescFile from '../../../components/SDescFile.vue'
|
|
3
|
+
import SInputFileUpload from '../../../components/SInputFileUpload.vue'
|
|
4
|
+
import { type TableCell } from '../../../composables/Table'
|
|
5
|
+
import { type FileUploadFieldData } from '../FieldData'
|
|
6
|
+
import { type FileDownloader } from '../FileDownloader'
|
|
7
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
8
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
9
|
+
import { Field } from './Field'
|
|
10
|
+
|
|
11
|
+
export class FileUploadField extends Field<FileUploadFieldData> {
|
|
12
|
+
downloader: FileDownloader
|
|
13
|
+
|
|
14
|
+
constructor(ctx: any, fieldData: FileUploadFieldData, downloader: FileDownloader) {
|
|
15
|
+
super(ctx, fieldData)
|
|
16
|
+
this.downloader = downloader
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
tableCell(v: any, _r: any): TableCell {
|
|
20
|
+
return {
|
|
21
|
+
type: 'text',
|
|
22
|
+
value: v
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
27
|
+
return {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
dataListItemComponent(): any {
|
|
31
|
+
return this.defineDataListItemComponent((value) => {
|
|
32
|
+
if (value === null) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
return h(SDescFile, {
|
|
36
|
+
item: value.map((filePath: any) => ({
|
|
37
|
+
name: filePath.split('/').pop(),
|
|
38
|
+
onDownload: () => this.downloader(filePath)
|
|
39
|
+
}))
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
inputEmptyValue(): any {
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
formInputComponent(): any {
|
|
49
|
+
return this.defineFormInputComponent((props, { emit }) => {
|
|
50
|
+
return () => h(SInputFileUpload, {
|
|
51
|
+
'size': 'mini',
|
|
52
|
+
'label': this.formInputLabel(),
|
|
53
|
+
'placeholder': this.placeholder() || undefined,
|
|
54
|
+
'help': this.help() || undefined,
|
|
55
|
+
'modelValue': props.modelValue,
|
|
56
|
+
'validation': props.validation,
|
|
57
|
+
'onUpdate:modelValue': (value: any) => {
|
|
58
|
+
emit('update:modelValue', value)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type TableCell } from '../../../composables/Table'
|
|
2
|
+
import { type IdFieldData } 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 IdField extends Field<IdFieldData> {
|
|
9
|
+
tableCell(v: any, _r: any): TableCell {
|
|
10
|
+
return {
|
|
11
|
+
type: 'text',
|
|
12
|
+
value: v.display,
|
|
13
|
+
link: v.path,
|
|
14
|
+
color: 'info'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
19
|
+
const number = new NumberFilterInput()
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
'=': number,
|
|
23
|
+
'!=': number
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
dataListItemComponent(): any {
|
|
28
|
+
throw new Error('Not implemented.')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
formInputComponent() {
|
|
32
|
+
throw new Error('Not implemented.')
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
import SInputText from '../../../components/SInputText.vue'
|
|
3
|
+
import SLink from '../../../components/SLink.vue'
|
|
4
|
+
import { type TableCell } from '../../../composables/Table'
|
|
5
|
+
import { type LinkFieldData } from '../FieldData'
|
|
6
|
+
import { type FilterOperator } from '../FilterOperator'
|
|
7
|
+
import { type FilterInput } from '../filter-inputs/FilterInput'
|
|
8
|
+
import { TextFilterInput } from '../filter-inputs/TextFilterInput'
|
|
9
|
+
import { Field } from './Field'
|
|
10
|
+
|
|
11
|
+
export class LinkField extends Field<LinkFieldData> {
|
|
12
|
+
tableCell(v: any, _r: any): TableCell {
|
|
13
|
+
return {
|
|
14
|
+
type: 'text',
|
|
15
|
+
value: v,
|
|
16
|
+
link: v,
|
|
17
|
+
color: 'info'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
availableFilters(): Partial<Record<FilterOperator, FilterInput>> {
|
|
22
|
+
const text = new TextFilterInput()
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
'=': text,
|
|
26
|
+
'!=': text
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
dataListItemComponent(): any {
|
|
31
|
+
return this.defineDataListItemComponent((value) => {
|
|
32
|
+
return h(SLink, { href: value }, () => value)
|
|
33
|
+
}, {
|
|
34
|
+
lineClamp: 1
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
formInputComponent(): any {
|
|
39
|
+
return this.defineFormInputComponent((props, { emit }) => {
|
|
40
|
+
return () => h(SInputText, {
|
|
41
|
+
'size': 'md',
|
|
42
|
+
'label': this.formInputLabel(),
|
|
43
|
+
'placeholder': this.placeholder() || undefined,
|
|
44
|
+
'help': this.help() || undefined,
|
|
45
|
+
'modelValue': props.modelValue,
|
|
46
|
+
'validation': props.validation,
|
|
47
|
+
'onUpdate:modelValue': (value: any) => {
|
|
48
|
+
emit('update:modelValue', value)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|