@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.
Files changed (55) hide show
  1. package/config/nuxt.js +53 -2
  2. package/config/vite.js +43 -5
  3. package/lib/blocks/lens/FieldContext.ts +5 -0
  4. package/lib/blocks/lens/FieldData.ts +140 -0
  5. package/lib/blocks/lens/FieldRegistry.ts +23 -0
  6. package/lib/blocks/lens/FileDownloader.ts +1 -0
  7. package/lib/blocks/lens/FilterOperator.ts +33 -0
  8. package/lib/blocks/lens/LensQuery.ts +10 -0
  9. package/lib/blocks/lens/LensResult.ts +20 -0
  10. package/lib/blocks/lens/ResourceFetcher.ts +3 -0
  11. package/lib/blocks/lens/Rule.ts +12 -0
  12. package/lib/blocks/lens/components/LensCatalog.vue +490 -0
  13. package/lib/blocks/lens/components/LensCatalogControl.vue +220 -0
  14. package/lib/blocks/lens/components/LensCatalogFooter.vue +46 -0
  15. package/lib/blocks/lens/components/LensCatalogStateFilter.vue +171 -0
  16. package/lib/blocks/lens/components/LensCatalogStateFilterCondition.vue +86 -0
  17. package/lib/blocks/lens/components/LensCatalogStateFilterGroup.vue +102 -0
  18. package/lib/blocks/lens/components/LensCatalogStateSort.vue +159 -0
  19. package/lib/blocks/lens/components/LensFormFilter.vue +169 -0
  20. package/lib/blocks/lens/components/LensFormFilterCondition.vue +205 -0
  21. package/lib/blocks/lens/components/LensFormFilterGroup.vue +175 -0
  22. package/lib/blocks/lens/components/LensFormOverride.vue +45 -0
  23. package/lib/blocks/lens/components/LensFormOverrideBase.vue +204 -0
  24. package/lib/blocks/lens/components/LensFormView.vue +347 -0
  25. package/lib/blocks/lens/components/LensTable.vue +154 -0
  26. package/lib/blocks/lens/composables/FieldFactory.ts +27 -0
  27. package/lib/blocks/lens/composables/FieldRegistry.ts +16 -0
  28. package/lib/blocks/lens/composables/FileDownloader.ts +10 -0
  29. package/lib/blocks/lens/composables/ResourceFetcher.ts +30 -0
  30. package/lib/blocks/lens/composables/SetupLens.ts +55 -0
  31. package/lib/blocks/lens/fields/ContentField.ts +34 -0
  32. package/lib/blocks/lens/fields/DateField.ts +66 -0
  33. package/lib/blocks/lens/fields/DatetimeField.ts +35 -0
  34. package/lib/blocks/lens/fields/Field.ts +244 -0
  35. package/lib/blocks/lens/fields/FileUploadField.ts +63 -0
  36. package/lib/blocks/lens/fields/IdField.ts +34 -0
  37. package/lib/blocks/lens/fields/LinkField.ts +53 -0
  38. package/lib/blocks/lens/fields/NumberField.ts +32 -0
  39. package/lib/blocks/lens/fields/RelatedManyField.ts +62 -0
  40. package/lib/blocks/lens/fields/SelectField.ts +198 -0
  41. package/lib/blocks/lens/fields/SlackMessageField.ts +34 -0
  42. package/lib/blocks/lens/fields/TextField.ts +46 -0
  43. package/lib/blocks/lens/fields/TextareaField.ts +49 -0
  44. package/lib/blocks/lens/filter-inputs/FilterInput.ts +72 -0
  45. package/lib/blocks/lens/filter-inputs/NumberFilterInput.ts +26 -0
  46. package/lib/blocks/lens/filter-inputs/SelectFilterInput.ts +76 -0
  47. package/lib/blocks/lens/filter-inputs/TextFilterInput.ts +26 -0
  48. package/lib/blocks/lens/validation/RuleMapper.ts +22 -0
  49. package/lib/components/SInputTextarea.vue +28 -10
  50. package/lib/components/STable.vue +230 -61
  51. package/lib/components/STableCell.vue +2 -2
  52. package/lib/composables/TableAnimation.ts +180 -0
  53. package/lib/support/Scroll.ts +263 -0
  54. package/lib/support/Utils.ts +1 -1
  55. 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
+ }