@kinetis/vector-ui 0.0.0-stage → 0.9.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/CHANGELOG.md +34 -0
- package/LICENSE +18 -0
- package/README.md +72 -2
- package/app/app.config.ts +63 -0
- package/app/app.vue +11 -0
- package/app/components/Vx/Action/Dialog.vue +107 -0
- package/app/components/Vx/Cell/Boolean.vue +10 -0
- package/app/components/Vx/Cell/Color.vue +12 -0
- package/app/components/Vx/Cell/Count.vue +12 -0
- package/app/components/Vx/Cell/Date.vue +10 -0
- package/app/components/Vx/Cell/DateTime.vue +10 -0
- package/app/components/Vx/Cell/Email.vue +9 -0
- package/app/components/Vx/Cell/Enum.vue +14 -0
- package/app/components/Vx/Cell/File.vue +30 -0
- package/app/components/Vx/Cell/Image.vue +38 -0
- package/app/components/Vx/Cell/Json.vue +18 -0
- package/app/components/Vx/Cell/KeyValue.vue +25 -0
- package/app/components/Vx/Cell/Markdown.vue +18 -0
- package/app/components/Vx/Cell/Money.vue +16 -0
- package/app/components/Vx/Cell/Number.vue +16 -0
- package/app/components/Vx/Cell/Relation.vue +16 -0
- package/app/components/Vx/Cell/RichText.vue +41 -0
- package/app/components/Vx/Cell/Text.vue +13 -0
- package/app/components/Vx/Cell/Url.vue +11 -0
- package/app/components/Vx/Cell.vue +25 -0
- package/app/components/Vx/CommandPalette.vue +118 -0
- package/app/components/Vx/Confirm.vue +19 -0
- package/app/components/Vx/Dashboard.vue +27 -0
- package/app/components/Vx/FieldsForm.vue +46 -0
- package/app/components/Vx/Import/Dialog.vue +131 -0
- package/app/components/Vx/Inbox.vue +49 -0
- package/app/components/Vx/Input/Boolean.vue +10 -0
- package/app/components/Vx/Input/Currency.vue +25 -0
- package/app/components/Vx/Input/DateTime.vue +38 -0
- package/app/components/Vx/Input/Enum.vue +21 -0
- package/app/components/Vx/Input/KeyValue.vue +78 -0
- package/app/components/Vx/Input/Markdown.vue +120 -0
- package/app/components/Vx/Input/Money.vue +47 -0
- package/app/components/Vx/Input/Number.vue +24 -0
- package/app/components/Vx/Input/Relation.vue +127 -0
- package/app/components/Vx/Input/Repeater.vue +134 -0
- package/app/components/Vx/Input/RichText.vue +182 -0
- package/app/components/Vx/Input/Text.vue +21 -0
- package/app/components/Vx/Input/Textarea.vue +20 -0
- package/app/components/Vx/Input/Upload.vue +89 -0
- package/app/components/Vx/Layout.vue +111 -0
- package/app/components/Vx/List/BulkBar.vue +44 -0
- package/app/components/Vx/List/BulkEdit.vue +91 -0
- package/app/components/Vx/List/ColumnManager.vue +74 -0
- package/app/components/Vx/List/FilterBar.vue +224 -0
- package/app/components/Vx/List/ViewMenu.vue +115 -0
- package/app/components/Vx/Notifications.vue +9 -0
- package/app/components/Vx/Resource/Audit.vue +82 -0
- package/app/components/Vx/Resource/Form.vue +351 -0
- package/app/components/Vx/Resource/RelationPanel.vue +217 -0
- package/app/components/Vx/Resource/Show.vue +35 -0
- package/app/components/Vx/Resource/Table.vue +513 -0
- package/app/components/Vx/Widget/Card.vue +80 -0
- package/app/components/Vx/Widget/Partition.vue +21 -0
- package/app/components/Vx/Widget/Progress.vue +22 -0
- package/app/components/Vx/Widget/Stat.vue +22 -0
- package/app/components/Vx/Widget/Table.vue +31 -0
- package/app/components/Vx/Widget/Trend.vue +37 -0
- package/app/composables/useConfirm.ts +34 -0
- package/app/composables/useLeaveGuard.ts +21 -0
- package/app/composables/useVectorApi.ts +74 -0
- package/app/composables/useVectorAuthStore.ts +252 -0
- package/app/composables/useVectorBranding.ts +45 -0
- package/app/composables/useVectorDashboards.ts +15 -0
- package/app/composables/useVectorExtensions.ts +44 -0
- package/app/composables/useVectorFields.ts +68 -0
- package/app/composables/useVectorForm.ts +36 -0
- package/app/composables/useVectorIdle.ts +39 -0
- package/app/composables/useVectorImpersonation.ts +78 -0
- package/app/composables/useVectorInputs.ts +61 -0
- package/app/composables/useVectorJobs.ts +62 -0
- package/app/composables/useVectorLocale.ts +70 -0
- package/app/composables/useVectorNotifications.ts +24 -0
- package/app/composables/useVectorNotificationsInbox.ts +26 -0
- package/app/composables/useVectorPreferences.ts +30 -0
- package/app/composables/useVectorResource.ts +305 -0
- package/app/composables/useVectorRuntime.ts +42 -0
- package/app/composables/useVectorSchema.ts +56 -0
- package/app/composables/useVectorT.ts +9 -0
- package/app/composables/useVectorViews.ts +25 -0
- package/app/i18n/ar/actions.ts +11 -0
- package/app/i18n/ar/common.ts +23 -0
- package/app/i18n/ar/dashboard.ts +28 -0
- package/app/i18n/ar/fields.ts +77 -0
- package/app/i18n/ar/index.ts +12 -0
- package/app/i18n/ar/jobs.ts +36 -0
- package/app/i18n/ar/list.ts +160 -0
- package/app/i18n/ar/record.ts +62 -0
- package/app/i18n/ar/settings.ts +10 -0
- package/app/i18n/ar/shell.ts +54 -0
- package/app/i18n/de/actions.ts +11 -0
- package/app/i18n/de/common.ts +23 -0
- package/app/i18n/de/dashboard.ts +28 -0
- package/app/i18n/de/fields.ts +77 -0
- package/app/i18n/de/index.ts +12 -0
- package/app/i18n/de/jobs.ts +36 -0
- package/app/i18n/de/list.ts +160 -0
- package/app/i18n/de/record.ts +62 -0
- package/app/i18n/de/settings.ts +10 -0
- package/app/i18n/de/shell.ts +54 -0
- package/app/i18n/en/actions.ts +11 -0
- package/app/i18n/en/common.ts +23 -0
- package/app/i18n/en/dashboard.ts +28 -0
- package/app/i18n/en/fields.ts +77 -0
- package/app/i18n/en/index.ts +12 -0
- package/app/i18n/en/jobs.ts +36 -0
- package/app/i18n/en/list.ts +160 -0
- package/app/i18n/en/record.ts +62 -0
- package/app/i18n/en/settings.ts +10 -0
- package/app/i18n/en/shell.ts +54 -0
- package/app/i18n/es/actions.ts +11 -0
- package/app/i18n/es/common.ts +23 -0
- package/app/i18n/es/dashboard.ts +28 -0
- package/app/i18n/es/fields.ts +77 -0
- package/app/i18n/es/index.ts +12 -0
- package/app/i18n/es/jobs.ts +36 -0
- package/app/i18n/es/list.ts +160 -0
- package/app/i18n/es/record.ts +62 -0
- package/app/i18n/es/settings.ts +10 -0
- package/app/i18n/es/shell.ts +54 -0
- package/app/i18n/fr/actions.ts +11 -0
- package/app/i18n/fr/common.ts +23 -0
- package/app/i18n/fr/dashboard.ts +28 -0
- package/app/i18n/fr/fields.ts +77 -0
- package/app/i18n/fr/index.ts +12 -0
- package/app/i18n/fr/jobs.ts +36 -0
- package/app/i18n/fr/list.ts +160 -0
- package/app/i18n/fr/record.ts +62 -0
- package/app/i18n/fr/settings.ts +10 -0
- package/app/i18n/fr/shell.ts +54 -0
- package/app/i18n/he/actions.ts +11 -0
- package/app/i18n/he/common.ts +23 -0
- package/app/i18n/he/dashboard.ts +28 -0
- package/app/i18n/he/fields.ts +77 -0
- package/app/i18n/he/index.ts +12 -0
- package/app/i18n/he/jobs.ts +36 -0
- package/app/i18n/he/list.ts +160 -0
- package/app/i18n/he/record.ts +62 -0
- package/app/i18n/he/settings.ts +10 -0
- package/app/i18n/he/shell.ts +54 -0
- package/app/i18n/index.ts +111 -0
- package/app/i18n/it/actions.ts +11 -0
- package/app/i18n/it/common.ts +23 -0
- package/app/i18n/it/dashboard.ts +28 -0
- package/app/i18n/it/fields.ts +77 -0
- package/app/i18n/it/index.ts +12 -0
- package/app/i18n/it/jobs.ts +36 -0
- package/app/i18n/it/list.ts +160 -0
- package/app/i18n/it/record.ts +62 -0
- package/app/i18n/it/settings.ts +10 -0
- package/app/i18n/it/shell.ts +54 -0
- package/app/i18n/ja/actions.ts +11 -0
- package/app/i18n/ja/common.ts +23 -0
- package/app/i18n/ja/dashboard.ts +28 -0
- package/app/i18n/ja/fields.ts +77 -0
- package/app/i18n/ja/index.ts +12 -0
- package/app/i18n/ja/jobs.ts +36 -0
- package/app/i18n/ja/list.ts +160 -0
- package/app/i18n/ja/record.ts +62 -0
- package/app/i18n/ja/settings.ts +10 -0
- package/app/i18n/ja/shell.ts +54 -0
- package/app/i18n/ko/actions.ts +11 -0
- package/app/i18n/ko/common.ts +23 -0
- package/app/i18n/ko/dashboard.ts +28 -0
- package/app/i18n/ko/fields.ts +77 -0
- package/app/i18n/ko/index.ts +12 -0
- package/app/i18n/ko/jobs.ts +36 -0
- package/app/i18n/ko/list.ts +160 -0
- package/app/i18n/ko/record.ts +62 -0
- package/app/i18n/ko/settings.ts +10 -0
- package/app/i18n/ko/shell.ts +54 -0
- package/app/i18n/nl/actions.ts +11 -0
- package/app/i18n/nl/common.ts +23 -0
- package/app/i18n/nl/dashboard.ts +28 -0
- package/app/i18n/nl/fields.ts +77 -0
- package/app/i18n/nl/index.ts +12 -0
- package/app/i18n/nl/jobs.ts +36 -0
- package/app/i18n/nl/list.ts +160 -0
- package/app/i18n/nl/record.ts +62 -0
- package/app/i18n/nl/settings.ts +10 -0
- package/app/i18n/nl/shell.ts +54 -0
- package/app/i18n/pl/actions.ts +11 -0
- package/app/i18n/pl/common.ts +23 -0
- package/app/i18n/pl/dashboard.ts +28 -0
- package/app/i18n/pl/fields.ts +77 -0
- package/app/i18n/pl/index.ts +12 -0
- package/app/i18n/pl/jobs.ts +36 -0
- package/app/i18n/pl/list.ts +160 -0
- package/app/i18n/pl/record.ts +62 -0
- package/app/i18n/pl/settings.ts +10 -0
- package/app/i18n/pl/shell.ts +54 -0
- package/app/i18n/pt/actions.ts +11 -0
- package/app/i18n/pt/common.ts +23 -0
- package/app/i18n/pt/dashboard.ts +28 -0
- package/app/i18n/pt/fields.ts +77 -0
- package/app/i18n/pt/index.ts +12 -0
- package/app/i18n/pt/jobs.ts +36 -0
- package/app/i18n/pt/list.ts +160 -0
- package/app/i18n/pt/record.ts +62 -0
- package/app/i18n/pt/settings.ts +10 -0
- package/app/i18n/pt/shell.ts +54 -0
- package/app/i18n/pt-BR/actions.ts +11 -0
- package/app/i18n/pt-BR/common.ts +23 -0
- package/app/i18n/pt-BR/dashboard.ts +28 -0
- package/app/i18n/pt-BR/fields.ts +77 -0
- package/app/i18n/pt-BR/index.ts +12 -0
- package/app/i18n/pt-BR/jobs.ts +36 -0
- package/app/i18n/pt-BR/list.ts +160 -0
- package/app/i18n/pt-BR/record.ts +62 -0
- package/app/i18n/pt-BR/settings.ts +10 -0
- package/app/i18n/pt-BR/shell.ts +54 -0
- package/app/i18n/ru/actions.ts +11 -0
- package/app/i18n/ru/common.ts +23 -0
- package/app/i18n/ru/dashboard.ts +28 -0
- package/app/i18n/ru/fields.ts +77 -0
- package/app/i18n/ru/index.ts +12 -0
- package/app/i18n/ru/jobs.ts +36 -0
- package/app/i18n/ru/list.ts +160 -0
- package/app/i18n/ru/record.ts +62 -0
- package/app/i18n/ru/settings.ts +10 -0
- package/app/i18n/ru/shell.ts +54 -0
- package/app/i18n/tr/actions.ts +11 -0
- package/app/i18n/tr/common.ts +23 -0
- package/app/i18n/tr/dashboard.ts +28 -0
- package/app/i18n/tr/fields.ts +77 -0
- package/app/i18n/tr/index.ts +12 -0
- package/app/i18n/tr/jobs.ts +36 -0
- package/app/i18n/tr/list.ts +160 -0
- package/app/i18n/tr/record.ts +62 -0
- package/app/i18n/tr/settings.ts +10 -0
- package/app/i18n/tr/shell.ts +54 -0
- package/app/i18n/uk/actions.ts +11 -0
- package/app/i18n/uk/common.ts +23 -0
- package/app/i18n/uk/dashboard.ts +28 -0
- package/app/i18n/uk/fields.ts +77 -0
- package/app/i18n/uk/index.ts +12 -0
- package/app/i18n/uk/jobs.ts +36 -0
- package/app/i18n/uk/list.ts +160 -0
- package/app/i18n/uk/record.ts +62 -0
- package/app/i18n/uk/settings.ts +10 -0
- package/app/i18n/uk/shell.ts +54 -0
- package/app/i18n/zh-Hans/actions.ts +11 -0
- package/app/i18n/zh-Hans/common.ts +23 -0
- package/app/i18n/zh-Hans/dashboard.ts +28 -0
- package/app/i18n/zh-Hans/fields.ts +77 -0
- package/app/i18n/zh-Hans/index.ts +12 -0
- package/app/i18n/zh-Hans/jobs.ts +36 -0
- package/app/i18n/zh-Hans/list.ts +160 -0
- package/app/i18n/zh-Hans/record.ts +62 -0
- package/app/i18n/zh-Hans/settings.ts +10 -0
- package/app/i18n/zh-Hans/shell.ts +54 -0
- package/app/layouts/default.vue +8 -0
- package/app/layouts/vector.vue +238 -0
- package/app/middleware/vector-auth.ts +16 -0
- package/app/pages/d/[name].vue +12 -0
- package/app/pages/index.vue +22 -0
- package/app/pages/jobs/[id].vue +52 -0
- package/app/pages/jobs/index.vue +42 -0
- package/app/pages/login.vue +109 -0
- package/app/pages/p/[name].vue +37 -0
- package/app/pages/r/[resource]/[id]/edit.vue +54 -0
- package/app/pages/r/[resource]/[id]/index.vue +143 -0
- package/app/pages/r/[resource]/create.vue +53 -0
- package/app/pages/r/[resource]/index.vue +29 -0
- package/app/pages/settings.vue +104 -0
- package/app/plugins/vector-csp.client.ts +14 -0
- package/app/plugins/vector-extensions.client.ts +84 -0
- package/app/plugins/vector-i18n.ts +76 -0
- package/app/types/vector.ts +438 -0
- package/app/utils/files.ts +14 -0
- package/app/utils/format.ts +100 -0
- package/app/utils/richtext.ts +305 -0
- package/app/utils/rules.ts +113 -0
- package/app/utils/translate.ts +45 -0
- package/app/utils/widgets.ts +11 -0
- package/nuxt.config.ts +83 -0
- package/package.json +83 -5
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The descriptor `GET /vector/schema` publishes, as the layer consumes it.
|
|
3
|
+
* Mirrors packages/vector's ResourceDescriptor::toArray().
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type VectorFieldType =
|
|
7
|
+
| 'id' | 'string' | 'text' | 'markdown' | 'number' | 'money' | 'boolean'
|
|
8
|
+
| 'enum' | 'date' | 'datetime' | 'email' | 'url' | 'password' | 'slug'
|
|
9
|
+
| 'color' | 'json' | 'keyValue' | 'currency' | 'file' | 'image'
|
|
10
|
+
| 'belongsTo' | 'hasOne' | 'hasMany' | 'manyToMany' | 'computed' | 'richtext'
|
|
11
|
+
|
|
12
|
+
/** A rich-text feature: a node or mark type the field's schema allows (or one a runtime extension added). */
|
|
13
|
+
export type VectorRichTextFeature = 'bold' | 'italic' | 'underline' | 'strike' | 'code' | 'heading' | 'list' | 'blockquote' | 'codeBlock' | 'horizontalRule' | 'link' | 'table' | 'image' | 'mention' | (string & {})
|
|
14
|
+
|
|
15
|
+
/** A rich-text field's schema and sources. */
|
|
16
|
+
export interface VectorRichText {
|
|
17
|
+
features: VectorRichTextFeature[]
|
|
18
|
+
/** The file or image field whose uploads images come from. */
|
|
19
|
+
imageField: string | null
|
|
20
|
+
/** The relation whose records `@` mentions. */
|
|
21
|
+
mentions: string | null
|
|
22
|
+
/** The stored value is HTML rendered through the schema, not the document. */
|
|
23
|
+
storesHtml: boolean
|
|
24
|
+
maxBytes: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What a computed field is shown as. */
|
|
28
|
+
export type VectorComputedAs = 'text' | 'number' | 'money' | 'boolean' | 'date' | 'datetime' | 'enum'
|
|
29
|
+
|
|
30
|
+
export type VectorView = 'index' | 'show' | 'create' | 'update'
|
|
31
|
+
|
|
32
|
+
export interface VectorEnumOption {
|
|
33
|
+
value: string | number
|
|
34
|
+
label: string
|
|
35
|
+
color: string | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface VectorRule {
|
|
39
|
+
constraint: string
|
|
40
|
+
args: unknown[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface VectorRelation {
|
|
44
|
+
/** hasOne/hasMany: the target's field that points back at the owner */
|
|
45
|
+
inverse?: string
|
|
46
|
+
kind: 'belongsTo' | 'hasOne' | 'hasMany' | 'manyToMany'
|
|
47
|
+
resource: string
|
|
48
|
+
titleField: string | null
|
|
49
|
+
/** belongsTo: the choices are the target records whose `on` equals this form's `field` */
|
|
50
|
+
dependsOn?: { field: string, on: string, label: string } | null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type VectorOperator = 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte' | 'in' | 'nin' | 'like' | 'nlike' | 'between' | 'null' | 'nnull'
|
|
54
|
+
|
|
55
|
+
export interface VectorField {
|
|
56
|
+
name: string
|
|
57
|
+
type: VectorFieldType
|
|
58
|
+
label: string
|
|
59
|
+
sortable: boolean
|
|
60
|
+
searchable: boolean
|
|
61
|
+
filterable: boolean
|
|
62
|
+
readonly: boolean
|
|
63
|
+
/** Editable in place in the list (inlineEditable()). */
|
|
64
|
+
inline?: boolean
|
|
65
|
+
/** Unique among the resource's records: a duplicate starts without it; an import may match by it. */
|
|
66
|
+
unique?: boolean
|
|
67
|
+
/** hasMany: the owned rows edited inside the parent's form */
|
|
68
|
+
repeater?: { fields: string[], min: number, max: number } | null
|
|
69
|
+
help: string | null
|
|
70
|
+
component: string | null
|
|
71
|
+
views: VectorView[]
|
|
72
|
+
rules: VectorRule[]
|
|
73
|
+
props: Record<string, unknown> | []
|
|
74
|
+
summary: 'sum' | 'avg' | 'min' | 'max' | 'count' | null
|
|
75
|
+
default: unknown
|
|
76
|
+
hasDefault: boolean
|
|
77
|
+
operators: VectorOperator[]
|
|
78
|
+
nullable?: boolean
|
|
79
|
+
valueType?: 'string' | 'int' | 'float' | 'bool' | 'timestamp' | 'date'
|
|
80
|
+
options?: VectorEnumOption[]
|
|
81
|
+
relation?: VectorRelation
|
|
82
|
+
conditions?: VectorFieldConditions
|
|
83
|
+
/** A computed field: shown as `as`, computed on the server from the `from` fields. */
|
|
84
|
+
computed?: { as: VectorComputedAs, from: string[] }
|
|
85
|
+
/** A rich-text field's schema. */
|
|
86
|
+
richtext?: VectorRichText
|
|
87
|
+
/** A Markdown field's toolbar. */
|
|
88
|
+
markdown?: { toolbar: string[] }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** One comparison against another field of the same form. */
|
|
92
|
+
export interface VectorCondition {
|
|
93
|
+
field: string
|
|
94
|
+
op: 'eq' | 'ne' | 'in' | 'nin' | 'null' | 'nnull'
|
|
95
|
+
value: unknown
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Form behaviour that depends on other fields; every condition of a kind must hold. */
|
|
99
|
+
export interface VectorFieldConditions {
|
|
100
|
+
visible: VectorCondition[]
|
|
101
|
+
required: VectorCondition[]
|
|
102
|
+
disabled: VectorCondition[]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface VectorFieldState {
|
|
106
|
+
visible: boolean
|
|
107
|
+
required: boolean
|
|
108
|
+
disabled: boolean
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface VectorAction {
|
|
112
|
+
name: string
|
|
113
|
+
label: string
|
|
114
|
+
icon: string | null
|
|
115
|
+
confirmation: string | null
|
|
116
|
+
destructive: boolean
|
|
117
|
+
standalone: boolean
|
|
118
|
+
showOnIndex: boolean
|
|
119
|
+
showOnDetail: boolean
|
|
120
|
+
fields: VectorField[]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface VectorActionResponse {
|
|
124
|
+
type: 'message' | 'danger' | 'redirect' | 'url' | 'download' | 'job'
|
|
125
|
+
message: string | null
|
|
126
|
+
url: string | null
|
|
127
|
+
affected: number
|
|
128
|
+
jobId?: number | null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type VectorWidgetType = 'stat' | 'trend' | 'partition' | 'progress' | 'table'
|
|
132
|
+
|
|
133
|
+
export interface VectorWidget {
|
|
134
|
+
name: string
|
|
135
|
+
label: string
|
|
136
|
+
type: VectorWidgetType
|
|
137
|
+
icon: string | null
|
|
138
|
+
ranges: Record<string, string>
|
|
139
|
+
width: 3 | 4 | 6 | 12
|
|
140
|
+
cacheSeconds: number
|
|
141
|
+
/** Whether the cached result is shared by every user, or kept per user. */
|
|
142
|
+
cacheShared?: boolean
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface VectorWidgetData {
|
|
146
|
+
type: VectorWidgetType
|
|
147
|
+
range: string
|
|
148
|
+
cached: boolean
|
|
149
|
+
format?: string
|
|
150
|
+
value?: number | null
|
|
151
|
+
previous?: number | null
|
|
152
|
+
caption?: string | null
|
|
153
|
+
points?: { x: string, y: number }[]
|
|
154
|
+
total?: number | null
|
|
155
|
+
slices?: { label: string, value: number, color?: string | null }[]
|
|
156
|
+
target?: number
|
|
157
|
+
columns?: { key: string, label: string, align?: 'start' | 'end' }[]
|
|
158
|
+
rows?: Record<string, unknown>[]
|
|
159
|
+
link?: string | null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface VectorDashboard {
|
|
163
|
+
name: string
|
|
164
|
+
label: string
|
|
165
|
+
icon: string | null
|
|
166
|
+
widgets: VectorWidget[]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface VectorJob {
|
|
170
|
+
id: number
|
|
171
|
+
kind: 'action' | 'export' | 'import'
|
|
172
|
+
resource: string
|
|
173
|
+
name: string
|
|
174
|
+
actorId: string
|
|
175
|
+
status: 'queued' | 'running' | 'done' | 'failed' | 'stalled'
|
|
176
|
+
total: number | null
|
|
177
|
+
processed: number
|
|
178
|
+
result: Record<string, unknown> | null
|
|
179
|
+
failure: string | null
|
|
180
|
+
hasFile: boolean
|
|
181
|
+
fileName: string | null
|
|
182
|
+
createdAt: string
|
|
183
|
+
updatedAt: string
|
|
184
|
+
finishedAt: string | null
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface VectorNotification {
|
|
188
|
+
id: number
|
|
189
|
+
kind: 'info' | 'success' | 'warning' | 'error'
|
|
190
|
+
title: string
|
|
191
|
+
body: string | null
|
|
192
|
+
link: string | null
|
|
193
|
+
readAt: string | null
|
|
194
|
+
createdAt: string
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface VectorAuditEntry {
|
|
198
|
+
id: number
|
|
199
|
+
event: string
|
|
200
|
+
actorId: string
|
|
201
|
+
impersonatorId: string | null
|
|
202
|
+
changes: Record<string, { from: unknown, to: unknown }>
|
|
203
|
+
createdAt: string
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export type VectorExportFormat = 'csv' | 'xlsx'
|
|
207
|
+
|
|
208
|
+
/** A language the admin can be shown in: its tag, its name in itself, and its direction. */
|
|
209
|
+
export interface VectorLocale {
|
|
210
|
+
code: string
|
|
211
|
+
name: string
|
|
212
|
+
dir: 'ltr' | 'rtl'
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface VectorSchemaI18n {
|
|
216
|
+
locale: string
|
|
217
|
+
locales: VectorLocale[]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** create adds every row; update changes the records a key column finds; upsert does either. */
|
|
221
|
+
export type VectorImportMode = 'create' | 'update' | 'upsert'
|
|
222
|
+
|
|
223
|
+
export interface VectorImportPreview {
|
|
224
|
+
headers: string[]
|
|
225
|
+
rows: number
|
|
226
|
+
sample: Record<string, string>[]
|
|
227
|
+
mapping: Record<string, string>
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** One condition set, `filter[field][op] = value`, all of which must hold; values as the query string carries them. */
|
|
231
|
+
export type VectorFilters = Record<string, Partial<Record<VectorOperator, string>>>
|
|
232
|
+
|
|
233
|
+
/** Alternatives: any one of these condition sets may hold. */
|
|
234
|
+
export type VectorFilterGroup = VectorFilters[]
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The list grammar's whole filter object: field conditions, `trashed`,
|
|
238
|
+
* and `or` / `and` holding more filter objects — `(a or b) and c` is
|
|
239
|
+
* `{c: …, or: [{a: …}, {b: …}]}`.
|
|
240
|
+
*/
|
|
241
|
+
export interface VectorFilterQuery {
|
|
242
|
+
[key: string]: Partial<Record<VectorOperator, string>> | string | VectorFilterQuery[] | undefined
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** The list grammar a saved view stores and a bulk "all matching" selection sends. */
|
|
246
|
+
export interface VectorListState {
|
|
247
|
+
filter?: VectorFilterQuery
|
|
248
|
+
q?: string
|
|
249
|
+
sort?: string
|
|
250
|
+
group?: string
|
|
251
|
+
columns?: string
|
|
252
|
+
perPage?: number
|
|
253
|
+
summaries?: boolean
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface VectorSelection {
|
|
257
|
+
ids?: (number | string)[]
|
|
258
|
+
all?: { filter?: VectorFilterQuery, q?: string }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface VectorSavedView {
|
|
262
|
+
id: number
|
|
263
|
+
resource: string
|
|
264
|
+
ownerId: string
|
|
265
|
+
name: string
|
|
266
|
+
shared: boolean
|
|
267
|
+
isDefault: boolean
|
|
268
|
+
/** the default of everyone who has none of their own */
|
|
269
|
+
resourceDefault: boolean
|
|
270
|
+
state: VectorListState
|
|
271
|
+
mine: boolean
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export interface VectorSettingsGroup {
|
|
275
|
+
name: string
|
|
276
|
+
label: string
|
|
277
|
+
icon: string | null
|
|
278
|
+
fields: VectorField[]
|
|
279
|
+
values: Record<string, unknown>
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface VectorBranding {
|
|
283
|
+
title: string
|
|
284
|
+
logoUrl: string | null
|
|
285
|
+
primaryColor: string | null
|
|
286
|
+
primaryColorDark: string | null
|
|
287
|
+
theme: 'system' | 'light' | 'dark' | null
|
|
288
|
+
footerText: string | null
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export interface VectorSearchResult {
|
|
292
|
+
resource: string
|
|
293
|
+
resourceLabel: string
|
|
294
|
+
id: number | string
|
|
295
|
+
title: string
|
|
296
|
+
/** the resource's searchSubtitle() field, as stored */
|
|
297
|
+
subtitle?: string | null
|
|
298
|
+
/** the image field to fetch the thumbnail from, when the record has one */
|
|
299
|
+
image?: string | null
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface VectorSort {
|
|
303
|
+
field: string
|
|
304
|
+
order: 'asc' | 'desc'
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* One node of a resource's layout. Every key is present; the ones a kind
|
|
309
|
+
* does not use are null or false. A `field` node names a field of the
|
|
310
|
+
* resource; the others hold `children`.
|
|
311
|
+
*/
|
|
312
|
+
export interface VectorLayoutNode {
|
|
313
|
+
kind: 'section' | 'tabs' | 'tab' | 'field'
|
|
314
|
+
label: string | null
|
|
315
|
+
description: string | null
|
|
316
|
+
collapsible: boolean
|
|
317
|
+
collapsed: boolean
|
|
318
|
+
/** A section's or tab's grid columns (1 to 4). */
|
|
319
|
+
columns: number | null
|
|
320
|
+
/** A field's width: 1 or 2 columns, or the whole row; null sizes it by its type. */
|
|
321
|
+
span: 1 | 2 | 'full' | null
|
|
322
|
+
name: string | null
|
|
323
|
+
children: VectorLayoutNode[] | null
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** How the form and the show page group the fields, per view. */
|
|
327
|
+
export interface VectorLayout {
|
|
328
|
+
form: VectorLayoutNode[]
|
|
329
|
+
show: VectorLayoutNode[]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export interface VectorResource {
|
|
333
|
+
slug: string
|
|
334
|
+
label: string
|
|
335
|
+
labelSingular: string
|
|
336
|
+
group: string | null
|
|
337
|
+
icon: string | null
|
|
338
|
+
primaryKey: { field: string, column?: string, type: 'int' | 'string' }
|
|
339
|
+
titleField: string
|
|
340
|
+
version: string | null
|
|
341
|
+
fields: VectorField[]
|
|
342
|
+
table: {
|
|
343
|
+
columns: string[]
|
|
344
|
+
defaultSort: VectorSort[]
|
|
345
|
+
perPage: number
|
|
346
|
+
perPageOptions: number[]
|
|
347
|
+
maxPerPage: number
|
|
348
|
+
search: string[]
|
|
349
|
+
groupable: string[]
|
|
350
|
+
summaries: string[]
|
|
351
|
+
exportLimit: number
|
|
352
|
+
bulkLimit: number
|
|
353
|
+
}
|
|
354
|
+
show: { fields: string[] }
|
|
355
|
+
/** Whether a record may be duplicated into a new one's form. */
|
|
356
|
+
replicable?: boolean
|
|
357
|
+
/** Sections, tabs and spans; null when the resource declares none. Optional for a descriptor from before layout existed. */
|
|
358
|
+
layout?: VectorLayout | null
|
|
359
|
+
softDelete: { field: string } | null
|
|
360
|
+
/** what a global search result shows besides its title */
|
|
361
|
+
searchResult?: { subtitle: string | null, image: string | null }
|
|
362
|
+
/** Whether global search covers this resource. */
|
|
363
|
+
globallySearchable?: boolean
|
|
364
|
+
actions: VectorAction[]
|
|
365
|
+
widgets: VectorWidget[]
|
|
366
|
+
permissions: { viewAny: boolean, create: boolean, update: boolean, delete: boolean, export: boolean, setDefaultView?: boolean }
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export interface VectorRecordPermissions {
|
|
370
|
+
update: boolean
|
|
371
|
+
delete: boolean
|
|
372
|
+
restore: boolean
|
|
373
|
+
forceDelete: boolean
|
|
374
|
+
actions: string[]
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export interface VectorMenuGroup {
|
|
378
|
+
label: string
|
|
379
|
+
resources: string[]
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export interface VectorSchema {
|
|
383
|
+
version: number
|
|
384
|
+
resources: Record<string, VectorResource>
|
|
385
|
+
menu: VectorMenuGroup[]
|
|
386
|
+
settings: { name: string, label: string, icon: string | null }[]
|
|
387
|
+
dashboards: { name: string, label: string, icon: string | null }[]
|
|
388
|
+
/** `exportFormats`: what the export menu offers — csv always, xlsx where the server can write it (in the queue). */
|
|
389
|
+
capabilities: { impersonation: boolean, queue: boolean, uploads?: boolean, exportFormats?: VectorExportFormat[] }
|
|
390
|
+
branding: VectorBranding | null
|
|
391
|
+
/** The language this descriptor is in, and the ones this user may switch to. */
|
|
392
|
+
i18n?: VectorSchemaI18n
|
|
393
|
+
/** ISO 4217 exponents of the currencies whose minor unit is not a hundredth */
|
|
394
|
+
minorUnits?: Record<string, number>
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export interface VectorMe {
|
|
398
|
+
id: number | string
|
|
399
|
+
name: string | null
|
|
400
|
+
preferences: Record<string, unknown>
|
|
401
|
+
impersonator: number | string | null
|
|
402
|
+
unreadNotifications: number
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export interface VectorRelationValue {
|
|
406
|
+
id: number | string
|
|
407
|
+
label: string
|
|
408
|
+
/** A choice list's option only: the target's search subtitle, when declared and visible. */
|
|
409
|
+
subtitle?: string | null
|
|
410
|
+
/** A choice list's option only: the image field to fetch its thumbnail from, when the record has one. */
|
|
411
|
+
image?: string | null
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export interface VectorUploaded {
|
|
415
|
+
key: string
|
|
416
|
+
name: string | null
|
|
417
|
+
size: number
|
|
418
|
+
type: string
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export interface VectorCountValue {
|
|
422
|
+
count: number
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export type VectorRecord = Record<string, unknown> & { id: number | string }
|
|
426
|
+
|
|
427
|
+
export interface VectorPage {
|
|
428
|
+
data: VectorRecord[]
|
|
429
|
+
currentPage: number
|
|
430
|
+
perPage: number
|
|
431
|
+
total: number
|
|
432
|
+
lastPage: number
|
|
433
|
+
summaries?: Record<string, number | null>
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function fieldProps(field: VectorField): Record<string, unknown> {
|
|
437
|
+
return Array.isArray(field.props) ? {} : field.props
|
|
438
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A file's bytes as a `data:` URL, for an <img>. The hosted shell's strict
|
|
3
|
+
* Content Security Policy admits `data:` images (`img-src 'self' data: https:`)
|
|
4
|
+
* but not `blob:` ones, so every image the admin fetches with its credentials
|
|
5
|
+
* is shown this way; a download may still use a blob link.
|
|
6
|
+
*/
|
|
7
|
+
export function dataUrl(blob: Blob): Promise<string> {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const reader = new FileReader()
|
|
10
|
+
reader.onload = () => resolve(String(reader.result))
|
|
11
|
+
reader.onerror = () => reject(reader.error)
|
|
12
|
+
reader.readAsDataURL(blob)
|
|
13
|
+
})
|
|
14
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const currencyFormatters = new Map<string, Intl.NumberFormat>()
|
|
2
|
+
|
|
3
|
+
// The UI's language, which dates, numbers and money format in; undefined (the
|
|
4
|
+
// browser's) until the i18n plugin sets it. The UI runs in the browser only,
|
|
5
|
+
// one app per page, so a module-level value is safe.
|
|
6
|
+
let formatLocale: string | undefined
|
|
7
|
+
|
|
8
|
+
/** Formats in `locale` from now on: the admin's current language. */
|
|
9
|
+
export function setFormatLocale(locale: string | undefined): void {
|
|
10
|
+
if (locale === formatLocale) return
|
|
11
|
+
formatLocale = locale
|
|
12
|
+
currencyFormatters.clear()
|
|
13
|
+
dateTime = null
|
|
14
|
+
date = null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// The server's ISO 4217 exponents for the codes that are not hundredths. The
|
|
18
|
+
// server stores minor units, so its table — not the browser's CLDR data, which
|
|
19
|
+
// differs for a few codes — decides where the decimal point goes.
|
|
20
|
+
const minorUnitExceptions = new Map<string, number>()
|
|
21
|
+
|
|
22
|
+
/** Takes the exponents the schema publishes. */
|
|
23
|
+
export function adoptMinorUnits(exponents: Record<string, number>): void {
|
|
24
|
+
minorUnitExceptions.clear()
|
|
25
|
+
for (const [code, digits] of Object.entries(exponents)) minorUnitExceptions.set(code, digits)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** How many decimals a money value in this currency has: 2 unless ISO 4217 says otherwise. */
|
|
29
|
+
export function minorDigits(currency: string | null | undefined): number {
|
|
30
|
+
if (!currency) return 2
|
|
31
|
+
return minorUnitExceptions.get(currency.toUpperCase()) ?? 2
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function formatMoney(minorUnits: number, currency: string): string {
|
|
35
|
+
const digits = minorDigits(currency)
|
|
36
|
+
const key = `${currency}:${digits}`
|
|
37
|
+
let formatter = currencyFormatters.get(key)
|
|
38
|
+
|
|
39
|
+
if (!formatter) {
|
|
40
|
+
formatter = new Intl.NumberFormat(formatLocale, { style: 'currency', currency, minimumFractionDigits: digits, maximumFractionDigits: digits })
|
|
41
|
+
currencyFormatters.set(key, formatter)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return formatter.format(minorUnits / 10 ** digits)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let dateTime: Intl.DateTimeFormat | null = null
|
|
48
|
+
let date: Intl.DateTimeFormat | null = null
|
|
49
|
+
|
|
50
|
+
export function formatDateTime(iso: string): string {
|
|
51
|
+
dateTime ??= new Intl.DateTimeFormat(formatLocale, { dateStyle: 'medium', timeStyle: 'short' })
|
|
52
|
+
return dateTime.format(new Date(iso))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function formatDate(iso: string): string {
|
|
56
|
+
// A date-only value is a calendar date, not an instant: keep it out of the local zone.
|
|
57
|
+
const [y, m, d] = iso.slice(0, 10).split('-').map(Number)
|
|
58
|
+
|
|
59
|
+
date ??= new Intl.DateTimeFormat(formatLocale, { dateStyle: 'medium' })
|
|
60
|
+
return date.format(new Date(y!, m! - 1, d!))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function formatNumber(value: number, decimals?: number): string {
|
|
64
|
+
return new Intl.NumberFormat(formatLocale, decimals === undefined ? {} : { minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(value)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A money field's currency: its fixed one, or the one its currencyField
|
|
69
|
+
* names in the record — through a relation for `order.currency`, whose
|
|
70
|
+
* value is the related record (or `{id, label, currency}`). Null when the
|
|
71
|
+
* record does not say.
|
|
72
|
+
*/
|
|
73
|
+
export function currencyOf(field: { props?: Record<string, unknown> | unknown[] | null }, record: Record<string, unknown> | undefined): string | null {
|
|
74
|
+
const props = field.props && !Array.isArray(field.props) ? field.props : {}
|
|
75
|
+
if (typeof props.currency === 'string') return props.currency
|
|
76
|
+
if (typeof props.currencyField !== 'string') return null
|
|
77
|
+
const value = props.currencyField.split('.').reduce<unknown>(
|
|
78
|
+
(current, key) => (current && typeof current === 'object' ? (current as Record<string, unknown>)[key] : undefined),
|
|
79
|
+
record,
|
|
80
|
+
)
|
|
81
|
+
return typeof value === 'string' && value !== '' ? value : null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const currencyNames = new Map<string, Intl.DisplayNames>()
|
|
85
|
+
|
|
86
|
+
/** A currency code with its name in the given language: `EUR · Euro`. */
|
|
87
|
+
export function currencyLabel(code: string, locale: string | undefined = formatLocale): string {
|
|
88
|
+
const key = locale ?? ''
|
|
89
|
+
let names = currencyNames.get(key)
|
|
90
|
+
if (!names) {
|
|
91
|
+
try {
|
|
92
|
+
names = new Intl.DisplayNames(locale ? [locale] : undefined, { type: 'currency' })
|
|
93
|
+
} catch {
|
|
94
|
+
return code
|
|
95
|
+
}
|
|
96
|
+
currencyNames.set(key, names)
|
|
97
|
+
}
|
|
98
|
+
const name = names.of(code)
|
|
99
|
+
return name && name !== code ? `${code} · ${name}` : code
|
|
100
|
+
}
|