@kinetis/vector-ui 0.0.0-stage → 0.9.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 (282) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +18 -0
  3. package/README.md +72 -2
  4. package/app/app.config.ts +63 -0
  5. package/app/app.vue +11 -0
  6. package/app/components/Vx/Action/Dialog.vue +107 -0
  7. package/app/components/Vx/Cell/Boolean.vue +10 -0
  8. package/app/components/Vx/Cell/Color.vue +12 -0
  9. package/app/components/Vx/Cell/Count.vue +12 -0
  10. package/app/components/Vx/Cell/Date.vue +10 -0
  11. package/app/components/Vx/Cell/DateTime.vue +10 -0
  12. package/app/components/Vx/Cell/Email.vue +9 -0
  13. package/app/components/Vx/Cell/Enum.vue +14 -0
  14. package/app/components/Vx/Cell/File.vue +30 -0
  15. package/app/components/Vx/Cell/Image.vue +38 -0
  16. package/app/components/Vx/Cell/Json.vue +18 -0
  17. package/app/components/Vx/Cell/KeyValue.vue +25 -0
  18. package/app/components/Vx/Cell/Markdown.vue +18 -0
  19. package/app/components/Vx/Cell/Money.vue +16 -0
  20. package/app/components/Vx/Cell/Number.vue +16 -0
  21. package/app/components/Vx/Cell/Relation.vue +16 -0
  22. package/app/components/Vx/Cell/RichText.vue +41 -0
  23. package/app/components/Vx/Cell/Text.vue +13 -0
  24. package/app/components/Vx/Cell/Url.vue +11 -0
  25. package/app/components/Vx/Cell.vue +25 -0
  26. package/app/components/Vx/CommandPalette.vue +118 -0
  27. package/app/components/Vx/Confirm.vue +19 -0
  28. package/app/components/Vx/Dashboard.vue +27 -0
  29. package/app/components/Vx/FieldsForm.vue +46 -0
  30. package/app/components/Vx/Import/Dialog.vue +131 -0
  31. package/app/components/Vx/Inbox.vue +49 -0
  32. package/app/components/Vx/Input/Boolean.vue +10 -0
  33. package/app/components/Vx/Input/Currency.vue +25 -0
  34. package/app/components/Vx/Input/DateTime.vue +38 -0
  35. package/app/components/Vx/Input/Enum.vue +21 -0
  36. package/app/components/Vx/Input/KeyValue.vue +78 -0
  37. package/app/components/Vx/Input/Markdown.vue +120 -0
  38. package/app/components/Vx/Input/Money.vue +47 -0
  39. package/app/components/Vx/Input/Number.vue +24 -0
  40. package/app/components/Vx/Input/Relation.vue +127 -0
  41. package/app/components/Vx/Input/Repeater.vue +134 -0
  42. package/app/components/Vx/Input/RichText.vue +182 -0
  43. package/app/components/Vx/Input/Text.vue +21 -0
  44. package/app/components/Vx/Input/Textarea.vue +20 -0
  45. package/app/components/Vx/Input/Upload.vue +89 -0
  46. package/app/components/Vx/Layout.vue +111 -0
  47. package/app/components/Vx/List/BulkBar.vue +44 -0
  48. package/app/components/Vx/List/BulkEdit.vue +91 -0
  49. package/app/components/Vx/List/ColumnManager.vue +74 -0
  50. package/app/components/Vx/List/FilterBar.vue +224 -0
  51. package/app/components/Vx/List/ViewMenu.vue +115 -0
  52. package/app/components/Vx/Notifications.vue +9 -0
  53. package/app/components/Vx/Resource/Audit.vue +82 -0
  54. package/app/components/Vx/Resource/Form.vue +351 -0
  55. package/app/components/Vx/Resource/RelationPanel.vue +217 -0
  56. package/app/components/Vx/Resource/Show.vue +35 -0
  57. package/app/components/Vx/Resource/Table.vue +513 -0
  58. package/app/components/Vx/Widget/Card.vue +80 -0
  59. package/app/components/Vx/Widget/Partition.vue +21 -0
  60. package/app/components/Vx/Widget/Progress.vue +22 -0
  61. package/app/components/Vx/Widget/Stat.vue +22 -0
  62. package/app/components/Vx/Widget/Table.vue +31 -0
  63. package/app/components/Vx/Widget/Trend.vue +37 -0
  64. package/app/composables/useConfirm.ts +34 -0
  65. package/app/composables/useLeaveGuard.ts +21 -0
  66. package/app/composables/useVectorApi.ts +74 -0
  67. package/app/composables/useVectorAuthStore.ts +252 -0
  68. package/app/composables/useVectorBranding.ts +45 -0
  69. package/app/composables/useVectorDashboards.ts +15 -0
  70. package/app/composables/useVectorExtensions.ts +44 -0
  71. package/app/composables/useVectorFields.ts +68 -0
  72. package/app/composables/useVectorForm.ts +36 -0
  73. package/app/composables/useVectorIdle.ts +39 -0
  74. package/app/composables/useVectorImpersonation.ts +78 -0
  75. package/app/composables/useVectorInputs.ts +61 -0
  76. package/app/composables/useVectorJobs.ts +62 -0
  77. package/app/composables/useVectorLocale.ts +70 -0
  78. package/app/composables/useVectorNotifications.ts +24 -0
  79. package/app/composables/useVectorNotificationsInbox.ts +26 -0
  80. package/app/composables/useVectorPreferences.ts +30 -0
  81. package/app/composables/useVectorResource.ts +305 -0
  82. package/app/composables/useVectorRuntime.ts +42 -0
  83. package/app/composables/useVectorSchema.ts +56 -0
  84. package/app/composables/useVectorT.ts +9 -0
  85. package/app/composables/useVectorViews.ts +25 -0
  86. package/app/i18n/ar/actions.ts +11 -0
  87. package/app/i18n/ar/common.ts +23 -0
  88. package/app/i18n/ar/dashboard.ts +28 -0
  89. package/app/i18n/ar/fields.ts +77 -0
  90. package/app/i18n/ar/index.ts +12 -0
  91. package/app/i18n/ar/jobs.ts +36 -0
  92. package/app/i18n/ar/list.ts +160 -0
  93. package/app/i18n/ar/record.ts +62 -0
  94. package/app/i18n/ar/settings.ts +10 -0
  95. package/app/i18n/ar/shell.ts +54 -0
  96. package/app/i18n/de/actions.ts +11 -0
  97. package/app/i18n/de/common.ts +23 -0
  98. package/app/i18n/de/dashboard.ts +28 -0
  99. package/app/i18n/de/fields.ts +77 -0
  100. package/app/i18n/de/index.ts +12 -0
  101. package/app/i18n/de/jobs.ts +36 -0
  102. package/app/i18n/de/list.ts +160 -0
  103. package/app/i18n/de/record.ts +62 -0
  104. package/app/i18n/de/settings.ts +10 -0
  105. package/app/i18n/de/shell.ts +54 -0
  106. package/app/i18n/en/actions.ts +11 -0
  107. package/app/i18n/en/common.ts +23 -0
  108. package/app/i18n/en/dashboard.ts +28 -0
  109. package/app/i18n/en/fields.ts +77 -0
  110. package/app/i18n/en/index.ts +12 -0
  111. package/app/i18n/en/jobs.ts +36 -0
  112. package/app/i18n/en/list.ts +160 -0
  113. package/app/i18n/en/record.ts +62 -0
  114. package/app/i18n/en/settings.ts +10 -0
  115. package/app/i18n/en/shell.ts +54 -0
  116. package/app/i18n/es/actions.ts +11 -0
  117. package/app/i18n/es/common.ts +23 -0
  118. package/app/i18n/es/dashboard.ts +28 -0
  119. package/app/i18n/es/fields.ts +77 -0
  120. package/app/i18n/es/index.ts +12 -0
  121. package/app/i18n/es/jobs.ts +36 -0
  122. package/app/i18n/es/list.ts +160 -0
  123. package/app/i18n/es/record.ts +62 -0
  124. package/app/i18n/es/settings.ts +10 -0
  125. package/app/i18n/es/shell.ts +54 -0
  126. package/app/i18n/fr/actions.ts +11 -0
  127. package/app/i18n/fr/common.ts +23 -0
  128. package/app/i18n/fr/dashboard.ts +28 -0
  129. package/app/i18n/fr/fields.ts +77 -0
  130. package/app/i18n/fr/index.ts +12 -0
  131. package/app/i18n/fr/jobs.ts +36 -0
  132. package/app/i18n/fr/list.ts +160 -0
  133. package/app/i18n/fr/record.ts +62 -0
  134. package/app/i18n/fr/settings.ts +10 -0
  135. package/app/i18n/fr/shell.ts +54 -0
  136. package/app/i18n/he/actions.ts +11 -0
  137. package/app/i18n/he/common.ts +23 -0
  138. package/app/i18n/he/dashboard.ts +28 -0
  139. package/app/i18n/he/fields.ts +77 -0
  140. package/app/i18n/he/index.ts +12 -0
  141. package/app/i18n/he/jobs.ts +36 -0
  142. package/app/i18n/he/list.ts +160 -0
  143. package/app/i18n/he/record.ts +62 -0
  144. package/app/i18n/he/settings.ts +10 -0
  145. package/app/i18n/he/shell.ts +54 -0
  146. package/app/i18n/index.ts +111 -0
  147. package/app/i18n/it/actions.ts +11 -0
  148. package/app/i18n/it/common.ts +23 -0
  149. package/app/i18n/it/dashboard.ts +28 -0
  150. package/app/i18n/it/fields.ts +77 -0
  151. package/app/i18n/it/index.ts +12 -0
  152. package/app/i18n/it/jobs.ts +36 -0
  153. package/app/i18n/it/list.ts +160 -0
  154. package/app/i18n/it/record.ts +62 -0
  155. package/app/i18n/it/settings.ts +10 -0
  156. package/app/i18n/it/shell.ts +54 -0
  157. package/app/i18n/ja/actions.ts +11 -0
  158. package/app/i18n/ja/common.ts +23 -0
  159. package/app/i18n/ja/dashboard.ts +28 -0
  160. package/app/i18n/ja/fields.ts +77 -0
  161. package/app/i18n/ja/index.ts +12 -0
  162. package/app/i18n/ja/jobs.ts +36 -0
  163. package/app/i18n/ja/list.ts +160 -0
  164. package/app/i18n/ja/record.ts +62 -0
  165. package/app/i18n/ja/settings.ts +10 -0
  166. package/app/i18n/ja/shell.ts +54 -0
  167. package/app/i18n/ko/actions.ts +11 -0
  168. package/app/i18n/ko/common.ts +23 -0
  169. package/app/i18n/ko/dashboard.ts +28 -0
  170. package/app/i18n/ko/fields.ts +77 -0
  171. package/app/i18n/ko/index.ts +12 -0
  172. package/app/i18n/ko/jobs.ts +36 -0
  173. package/app/i18n/ko/list.ts +160 -0
  174. package/app/i18n/ko/record.ts +62 -0
  175. package/app/i18n/ko/settings.ts +10 -0
  176. package/app/i18n/ko/shell.ts +54 -0
  177. package/app/i18n/nl/actions.ts +11 -0
  178. package/app/i18n/nl/common.ts +23 -0
  179. package/app/i18n/nl/dashboard.ts +28 -0
  180. package/app/i18n/nl/fields.ts +77 -0
  181. package/app/i18n/nl/index.ts +12 -0
  182. package/app/i18n/nl/jobs.ts +36 -0
  183. package/app/i18n/nl/list.ts +160 -0
  184. package/app/i18n/nl/record.ts +62 -0
  185. package/app/i18n/nl/settings.ts +10 -0
  186. package/app/i18n/nl/shell.ts +54 -0
  187. package/app/i18n/pl/actions.ts +11 -0
  188. package/app/i18n/pl/common.ts +23 -0
  189. package/app/i18n/pl/dashboard.ts +28 -0
  190. package/app/i18n/pl/fields.ts +77 -0
  191. package/app/i18n/pl/index.ts +12 -0
  192. package/app/i18n/pl/jobs.ts +36 -0
  193. package/app/i18n/pl/list.ts +160 -0
  194. package/app/i18n/pl/record.ts +62 -0
  195. package/app/i18n/pl/settings.ts +10 -0
  196. package/app/i18n/pl/shell.ts +54 -0
  197. package/app/i18n/pt/actions.ts +11 -0
  198. package/app/i18n/pt/common.ts +23 -0
  199. package/app/i18n/pt/dashboard.ts +28 -0
  200. package/app/i18n/pt/fields.ts +77 -0
  201. package/app/i18n/pt/index.ts +12 -0
  202. package/app/i18n/pt/jobs.ts +36 -0
  203. package/app/i18n/pt/list.ts +160 -0
  204. package/app/i18n/pt/record.ts +62 -0
  205. package/app/i18n/pt/settings.ts +10 -0
  206. package/app/i18n/pt/shell.ts +54 -0
  207. package/app/i18n/pt-BR/actions.ts +11 -0
  208. package/app/i18n/pt-BR/common.ts +23 -0
  209. package/app/i18n/pt-BR/dashboard.ts +28 -0
  210. package/app/i18n/pt-BR/fields.ts +77 -0
  211. package/app/i18n/pt-BR/index.ts +12 -0
  212. package/app/i18n/pt-BR/jobs.ts +36 -0
  213. package/app/i18n/pt-BR/list.ts +160 -0
  214. package/app/i18n/pt-BR/record.ts +62 -0
  215. package/app/i18n/pt-BR/settings.ts +10 -0
  216. package/app/i18n/pt-BR/shell.ts +54 -0
  217. package/app/i18n/ru/actions.ts +11 -0
  218. package/app/i18n/ru/common.ts +23 -0
  219. package/app/i18n/ru/dashboard.ts +28 -0
  220. package/app/i18n/ru/fields.ts +77 -0
  221. package/app/i18n/ru/index.ts +12 -0
  222. package/app/i18n/ru/jobs.ts +36 -0
  223. package/app/i18n/ru/list.ts +160 -0
  224. package/app/i18n/ru/record.ts +62 -0
  225. package/app/i18n/ru/settings.ts +10 -0
  226. package/app/i18n/ru/shell.ts +54 -0
  227. package/app/i18n/tr/actions.ts +11 -0
  228. package/app/i18n/tr/common.ts +23 -0
  229. package/app/i18n/tr/dashboard.ts +28 -0
  230. package/app/i18n/tr/fields.ts +77 -0
  231. package/app/i18n/tr/index.ts +12 -0
  232. package/app/i18n/tr/jobs.ts +36 -0
  233. package/app/i18n/tr/list.ts +160 -0
  234. package/app/i18n/tr/record.ts +62 -0
  235. package/app/i18n/tr/settings.ts +10 -0
  236. package/app/i18n/tr/shell.ts +54 -0
  237. package/app/i18n/uk/actions.ts +11 -0
  238. package/app/i18n/uk/common.ts +23 -0
  239. package/app/i18n/uk/dashboard.ts +28 -0
  240. package/app/i18n/uk/fields.ts +77 -0
  241. package/app/i18n/uk/index.ts +12 -0
  242. package/app/i18n/uk/jobs.ts +36 -0
  243. package/app/i18n/uk/list.ts +160 -0
  244. package/app/i18n/uk/record.ts +62 -0
  245. package/app/i18n/uk/settings.ts +10 -0
  246. package/app/i18n/uk/shell.ts +54 -0
  247. package/app/i18n/zh-Hans/actions.ts +11 -0
  248. package/app/i18n/zh-Hans/common.ts +23 -0
  249. package/app/i18n/zh-Hans/dashboard.ts +28 -0
  250. package/app/i18n/zh-Hans/fields.ts +77 -0
  251. package/app/i18n/zh-Hans/index.ts +12 -0
  252. package/app/i18n/zh-Hans/jobs.ts +36 -0
  253. package/app/i18n/zh-Hans/list.ts +160 -0
  254. package/app/i18n/zh-Hans/record.ts +62 -0
  255. package/app/i18n/zh-Hans/settings.ts +10 -0
  256. package/app/i18n/zh-Hans/shell.ts +54 -0
  257. package/app/layouts/default.vue +8 -0
  258. package/app/layouts/vector.vue +238 -0
  259. package/app/middleware/vector-auth.ts +16 -0
  260. package/app/pages/d/[name].vue +12 -0
  261. package/app/pages/index.vue +22 -0
  262. package/app/pages/jobs/[id].vue +52 -0
  263. package/app/pages/jobs/index.vue +42 -0
  264. package/app/pages/login.vue +109 -0
  265. package/app/pages/p/[name].vue +37 -0
  266. package/app/pages/r/[resource]/[id]/edit.vue +54 -0
  267. package/app/pages/r/[resource]/[id]/index.vue +143 -0
  268. package/app/pages/r/[resource]/create.vue +53 -0
  269. package/app/pages/r/[resource]/index.vue +29 -0
  270. package/app/pages/settings.vue +104 -0
  271. package/app/plugins/vector-csp.client.ts +14 -0
  272. package/app/plugins/vector-extensions.client.ts +84 -0
  273. package/app/plugins/vector-i18n.ts +76 -0
  274. package/app/types/vector.ts +438 -0
  275. package/app/utils/files.ts +14 -0
  276. package/app/utils/format.ts +100 -0
  277. package/app/utils/richtext.ts +305 -0
  278. package/app/utils/rules.ts +113 -0
  279. package/app/utils/translate.ts +45 -0
  280. package/app/utils/widgets.ts +11 -0
  281. package/nuxt.config.ts +83 -0
  282. package/package.json +82 -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
+ }