@ditojs/admin 2.2.1 → 2.2.3

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.
@@ -1,4 +1,5 @@
1
1
  import { isArray, asArray, labelize } from '@ditojs/utils'
2
+ import { getNamedSchemas, processNestedSchemaDefaults } from './schema'
2
3
 
3
4
  export const filterComponents = {
4
5
  'text'(filter) {
@@ -24,17 +25,19 @@ export const filterComponents = {
24
25
  operator: filter.operators
25
26
  ? {
26
27
  type: 'select',
28
+ width: '2/5',
27
29
  options: isArray(filter.operators)
28
30
  ? options.filter(
29
31
  option => filter.operators.includes(option.value)
30
32
  )
31
33
  : options,
32
- width: '40%'
34
+ clearable: true
33
35
  }
34
36
  : null,
35
37
  text: {
36
38
  type: 'text',
37
- width: filter.operators ? '60%' : 'fill'
39
+ width: filter.operators ? '3/5' : 'fill',
40
+ clearable: true
38
41
  }
39
42
  }
40
43
  },
@@ -49,19 +52,22 @@ export const filterComponents = {
49
52
  return {
50
53
  from: {
51
54
  type: 'datetime',
52
- width: '50%',
53
- dateFormat
55
+ width: '1/2',
56
+ dateFormat,
57
+ clearable: true
54
58
  },
55
59
  to: {
56
60
  type: 'datetime',
57
- width: '50%',
58
- dateFormat
61
+ width: '1/2',
62
+ dateFormat,
63
+ clearable: true
59
64
  }
60
65
  }
61
66
  }
62
67
  }
63
68
 
64
- export function getFiltersPanel(filters, dataPath, proxy) {
69
+ export function createFiltersPanel(api, filters, dataPath, proxy) {
70
+ const { sticky, ...filterSchemas } = filters
65
71
  const panel = {
66
72
  type: 'panel',
67
73
  label: 'Filters',
@@ -69,6 +75,8 @@ export function getFiltersPanel(filters, dataPath, proxy) {
69
75
  target: dataPath,
70
76
  // Override the default value
71
77
  disabled: false,
78
+ sticky,
79
+
72
80
  // NOTE: On panels, the data() callback does something else than on normal
73
81
  // schema: It produces the `data` property to be passed to the panel's
74
82
  // schema, not the data to be used for the panel component directly.
@@ -78,50 +86,83 @@ export function getFiltersPanel(filters, dataPath, proxy) {
78
86
  proxy.query
79
87
  )
80
88
  },
81
- components: getFiltersComponents(filters),
82
- buttons: {
83
- clear: {
84
- text: 'Clear',
85
- events: {
86
- click({ schemaComponent }) {
87
- schemaComponent.resetData()
88
- schemaComponent.applyFilters()
89
- }
90
- }
89
+
90
+ components: createFiltersComponents(filterSchemas),
91
+ buttons: createFiltersButtons(false),
92
+ panelButtons: createFiltersButtons(true),
93
+
94
+ events: {
95
+ change() {
96
+ this.applyFilters()
97
+ }
98
+ },
99
+
100
+ computed: {
101
+ filters() {
102
+ return formatFiltersData(this.schema, this.data)
91
103
  },
92
104
 
93
- filter: {
94
- type: 'submit',
95
- text: 'Filter',
96
- events: {
97
- click({ schemaComponent }) {
98
- schemaComponent.applyFilters()
99
- }
100
- }
105
+ hasFilters() {
106
+ return this.filters.length > 0
101
107
  }
102
108
  },
109
+
103
110
  methods: {
104
111
  applyFilters() {
105
112
  proxy.query = {
106
113
  ...proxy.query,
107
- filter: formatFiltersData(this.schema, this.data),
114
+ filter: this.filters,
108
115
  // Clear pagination when applying or clearing filters:
109
116
  page: undefined
110
117
  }
118
+ },
119
+
120
+ clearFilters() {
121
+ this.resetData()
122
+ this.applyFilters()
111
123
  }
112
124
  }
113
125
  }
126
+ processNestedSchemaDefaults(api, panel)
114
127
  return panel
115
128
  }
116
129
 
117
- function getFiltersComponents(filters) {
130
+ function createFiltersButtons(small) {
131
+ return {
132
+ clear: {
133
+ type: 'button',
134
+ text: small ? null : 'Clear',
135
+ disabled: ({ schemaComponent }) => !schemaComponent.hasFilters,
136
+ events: {
137
+ click({ schemaComponent }) {
138
+ // Since panel buttons are outside of the schema, we need to use the
139
+ // schema component received from the initialize event below:
140
+ schemaComponent.clearFilters()
141
+ }
142
+ }
143
+ },
144
+
145
+ submit: {
146
+ type: 'submit',
147
+ text: small ? null : 'Filter',
148
+ visible: !small,
149
+ events: {
150
+ click({ schemaComponent }) {
151
+ schemaComponent.applyFilters()
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ function createFiltersComponents(filters) {
118
159
  const comps = {}
119
- for (const filter of Object.values(filters || {})) {
160
+ for (const filter of Object.values(getNamedSchemas(filters) || {})) {
120
161
  // Support both custom forms and default filter components, through the
121
162
  // `filterComponents` registry. Even for default filters, still use the
122
163
  // properties in `filter` as the base for `form`, so things like `label`
123
164
  // can be changed on the resulting form.
124
- const { filter: type, ...form } = filter
165
+ const { filter: type, width, ...form } = filter
125
166
  const components = type
126
167
  ? filterComponents[type]?.(filter)
127
168
  : filter.components
@@ -142,6 +183,7 @@ function getFiltersComponents(filters) {
142
183
  comps[filter.name] = {
143
184
  label: form.label,
144
185
  type: 'object',
186
+ width,
145
187
  default: () => ({}),
146
188
  form,
147
189
  inlined: true
@@ -175,7 +217,7 @@ function formatFiltersData(schema, data) {
175
217
  }
176
218
  }
177
219
  }
178
- return filters.length ? filters : undefined
220
+ return filters
179
221
  }
180
222
 
181
223
  function parseFiltersData(schema, query) {
@@ -0,0 +1,46 @@
1
+ import { isArray, asArray } from '@ditojs/utils'
2
+
3
+ export function formatQuery(query) {
4
+ const entries = query
5
+ ? isArray(query)
6
+ ? query
7
+ : Object.entries(query)
8
+ : []
9
+ return (
10
+ new URLSearchParams(
11
+ // Expand array values into multiple entries under the same key, so
12
+ // `formatQuery({ foo: [1, 2], bar: 3 })` => 'foo=1&foo=2&bar=3'.
13
+ entries.reduce(
14
+ (entries, [key, value]) => {
15
+ for (const val of asArray(value)) {
16
+ entries.push([key, val])
17
+ }
18
+ return entries
19
+ },
20
+ []
21
+ )
22
+ )
23
+ .toString()
24
+ // decode all these encoded characters to have the same behavior as
25
+ // vue-router's own query encoding.
26
+ .replaceAll(/%(?:21|24|28|29|2C|2F|3A|3B|3D|3F|40)/g, decodeURIComponent)
27
+ )
28
+ }
29
+
30
+ export function replaceRoute({ path, query, hash }) {
31
+ // Preserve `history.state`, see:
32
+ // https://router.vuejs.org/guide/migration/#usage-of-history-state
33
+ history.replaceState(
34
+ history.state,
35
+ null,
36
+ `${
37
+ location.origin
38
+ }${
39
+ path ?? location.pathname
40
+ }?${
41
+ query ? formatQuery(query) : location.search.slice(1)
42
+ }${
43
+ hash ?? location.hash
44
+ }`
45
+ )
46
+ }
@@ -55,7 +55,7 @@ export function iterateSchemaComponents(schemas, callback) {
55
55
 
56
56
  export function iterateNestedSchemaComponents(schema, callback) {
57
57
  return schema
58
- ? iterateSchemaComponents([schema, ...getAllTabSchemas(schema)], callback)
58
+ ? iterateSchemaComponents([schema, ...getTabSchemas(schema)], callback)
59
59
  : undefined
60
60
  }
61
61
 
@@ -191,7 +191,12 @@ export async function resolveSchemaComponents(schemas) {
191
191
  await mapConcurrently(Object.values(schemas || {}), resolveSchemaComponent)
192
192
  }
193
193
 
194
- export async function processSchemaComponents(api, schema, routes, level) {
194
+ export async function processSchemaComponents(
195
+ api,
196
+ schema,
197
+ routes = null,
198
+ level = 0
199
+ ) {
195
200
  const promises = []
196
201
  const process = (component, name, relativeLevel) => {
197
202
  promises.push(
@@ -206,21 +211,28 @@ export async function processSchemaComponents(api, schema, routes, level) {
206
211
  }
207
212
 
208
213
  iterateNestedSchemaComponents(schema, process)
209
- iterateSchemaComponents(getAllPanelSchemas(schema), process)
214
+ iterateSchemaComponents(getPanelSchemas(schema), process)
210
215
 
211
216
  await Promise.all(promises)
212
217
  }
213
218
 
214
- export async function processSchemaComponent(api, schema, name, routes, level) {
215
- processDefaults(api, schema)
219
+ export async function processSchemaComponent(
220
+ api,
221
+ schema,
222
+ name,
223
+ routes = null,
224
+ level = 0
225
+ ) {
226
+ processSchemaDefaults(api, schema)
216
227
 
217
228
  await Promise.all([
218
229
  // Also process nested panel schemas.
219
- mapConcurrently(getAllPanelSchemas(schema), panel =>
220
- processSchemaComponents(api, panel, routes, level)
230
+ mapConcurrently(
231
+ getPanelSchemas(schema),
232
+ panel => processSchemaComponents(api, panel, routes, level)
221
233
  ),
222
234
  // Delegate schema processing to the actual type components.
223
- await getTypeOptions(schema)?.processSchema?.(
235
+ getTypeOptions(schema)?.processSchema?.(
224
236
  api,
225
237
  schema,
226
238
  name,
@@ -235,8 +247,8 @@ export async function processView(component, api, schema, name) {
235
247
  throw new Error(`Invalid view schema: '${getSchemaIdentifier(schema)}'`)
236
248
  }
237
249
  processRouteSchema(api, schema, name)
238
- processDefaults(api, schema)
239
- await resolveNestedSchemas(api, schema)
250
+ processSchemaDefaults(api, schema)
251
+ await processNestedSchemas(api, schema)
240
252
  const children = []
241
253
  await processSchemaComponents(api, schema, children, 0)
242
254
  return {
@@ -250,7 +262,7 @@ export async function processView(component, api, schema, name) {
250
262
  }
251
263
  }
252
264
 
253
- export function processDefaults(api, schema) {
265
+ export function processSchemaDefaults(api, schema) {
254
266
  let defaults = api.defaults[schema.type]
255
267
  if (defaults) {
256
268
  if (isFunction(defaults)) {
@@ -268,6 +280,20 @@ export function processDefaults(api, schema) {
268
280
  }
269
281
  }
270
282
 
283
+ export function processNestedSchemaDefaults(api, schema) {
284
+ // Process defaults for nested schemas. Note that this is also done when
285
+ // calling `processSchemaComponents()`, but that function is async, and we
286
+ // need a sync version that only handles the defaults for filters, see
287
+ // `getFiltersPanel()`.
288
+ iterateNestedSchemaComponents(schema, component => {
289
+ processSchemaDefaults(api, component)
290
+ const forms = getFormSchemas(component)
291
+ for (const form of Object.values(forms)) {
292
+ processNestedSchemaDefaults(api, form)
293
+ }
294
+ })
295
+ }
296
+
271
297
  export function processRouteSchema(api, schema, name) {
272
298
  // Used for view and source schemas, see SourceMixin.
273
299
  schema.name = name
@@ -304,8 +330,8 @@ export async function processForm(api, schema) {
304
330
  if (!isForm(schema)) {
305
331
  throw new Error(`Invalid form schema: '${getSchemaIdentifier(schema)}'`)
306
332
  }
307
- processDefaults(api, schema)
308
- await resolveNestedSchemas(api, schema)
333
+ processSchemaDefaults(api, schema)
334
+ await processNestedSchemas(api, schema)
309
335
  return schema
310
336
  }
311
337
 
@@ -314,7 +340,7 @@ export async function processTab(api, schema) {
314
340
  if (!isTab(schema)) {
315
341
  throw new Error(`Invalid tab schema: '${getSchemaIdentifier(schema)}'`)
316
342
  }
317
- processDefaults(api, schema)
343
+ processSchemaDefaults(api, schema)
318
344
  return schema
319
345
  }
320
346
 
@@ -323,11 +349,11 @@ export async function processPanel(api, schema) {
323
349
  if (!isPanel(schema)) {
324
350
  throw new Error(`Invalid panel schema: '${getSchemaIdentifier(schema)}'`)
325
351
  }
326
- processDefaults(api, schema)
352
+ processSchemaDefaults(api, schema)
327
353
  return schema
328
354
  }
329
355
 
330
- export async function resolveNestedSchemas(api, schema) {
356
+ export async function processNestedSchemas(api, schema) {
331
357
  const { tabs, panels } = schema
332
358
  if (tabs) {
333
359
  schema.tabs = await resolveSchemas(
@@ -392,7 +418,7 @@ export function getViewEditPath(schema, context) {
392
418
  }
393
419
 
394
420
  export function getFormSchemas(schema, context, modifyForm) {
395
- const viewSchema = getViewFormSchema(schema, context)
421
+ const viewSchema = context && getViewFormSchema(schema, context)
396
422
  if (viewSchema) {
397
423
  schema = viewSchema
398
424
  } else if (schema.view) {
@@ -413,7 +439,7 @@ export function getFormSchemas(schema, context, modifyForm) {
413
439
  return Object.fromEntries(
414
440
  Object.entries(forms).map(([type, form]) => {
415
441
  // Support `schema.components` callbacks to create components on the fly.
416
- if (isFunction(form.components)) {
442
+ if (context && isFunction(form.components)) {
417
443
  form = {
418
444
  ...form,
419
445
  components: form.components(context)
@@ -760,10 +786,10 @@ export function processSchemaData(
760
786
  }
761
787
 
762
788
  processComponents(schema.components)
763
- for (const tab of getAllTabSchemas(schema)) {
789
+ for (const tab of getTabSchemas(schema)) {
764
790
  processComponents(tab.components)
765
791
  }
766
- for (const panel of getAllPanelSchemas(schema)) {
792
+ for (const panel of getPanelSchemas(schema)) {
767
793
  processComponents(panel.components)
768
794
  }
769
795
 
@@ -871,21 +897,23 @@ export function getPanelEntries(
871
897
  return panelEntries
872
898
  }
873
899
 
874
- export function getAllTabSchemas(schema) {
900
+ export function getTabSchemas(schema) {
875
901
  return schema?.tabs ? Object.values(schema.tabs) : []
876
902
  }
877
903
 
878
- export function getAllPanelSchemas(schema) {
879
- return getAllPanelEntries(schema).map(entry => entry.schema)
904
+ export function getPanelSchemas(schema) {
905
+ return schema?.panels ? Object.values(schema.panels) : []
880
906
  }
881
907
 
882
908
  export function getAllPanelEntries(
909
+ api,
883
910
  schema,
884
911
  dataPath = null,
885
912
  schemaComponent = null,
886
913
  tabComponent = null
887
914
  ) {
888
915
  const panelSchema = getTypeOptions(schema)?.getPanelSchema?.(
916
+ api,
889
917
  schema,
890
918
  dataPath,
891
919
  schemaComponent
package/src/verbs.js CHANGED
@@ -4,9 +4,11 @@ export default [
4
4
  'submit', 'submitted',
5
5
  'delete', 'deleted',
6
6
  'edit', 'edited',
7
+ 'clear', 'cleared',
7
8
  'close', 'closed',
8
9
  'cancel', 'cancelled',
9
- 'drag', 'dragged'
10
+ 'drag', 'dragged',
11
+ 'login', 'logged in'
10
12
  ].reduce((verbs, verb) => {
11
13
  verbs[verb] = verb
12
14
  return verbs