@crombolo/backoffice-host 0.11.0 → 0.13.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/package.json +2 -2
- package/plugin/addresses-dashboard.js +100 -0
- package/plugin/assistant/umbraco-package.json +1 -1
- package/plugin/bundles-installed.js +22 -9
- package/plugin/bundles-marketplace.js +12 -2
- package/plugin/changes-dashboard.js +245 -8
- package/plugin/forms-client.js +7 -0
- package/plugin/forms-entries.js +2 -1
- package/plugin/forms-layout.js +128 -0
- package/plugin/forms-overview.js +484 -134
- package/plugin/localizations/umbraco-package.json +1 -1
- package/plugin/report-client.js +21 -0
- package/plugin/seo/umbraco-package.json +1 -1
- package/plugin/tsx-editors.js +5 -5
- package/plugin/umbraco-package.json +142 -1
- package/plugin/webhook-storage.js +107 -0
- package/plugin/welcome-dashboard.js +1 -1
- package/plugin/workflow-client.js +40 -0
- package/plugin/workflow-designer.js +529 -0
- package/plugin/workflow-graph.js +132 -0
- package/plugin/workflow-requests.js +151 -0
- package/plugin/workflow-tab.js +219 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The form designer's layout operations: pages, groups and fields moved, added
|
|
3
|
+
* and removed. Pure — each takes a form and answers a new one, leaving the
|
|
4
|
+
* argument untouched — so the designer's element only has to render, and each
|
|
5
|
+
* operation is tested on its own (tests/forms-layout.test.ts).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const copy = (form) => structuredClone(form)
|
|
9
|
+
|
|
10
|
+
/** `list` with the item at `from` moved to `to`, clamped to the list. */
|
|
11
|
+
export function moved(list, from, to) {
|
|
12
|
+
const out = [...list]
|
|
13
|
+
if (from < 0 || from >= out.length) return out
|
|
14
|
+
const [item] = out.splice(from, 1)
|
|
15
|
+
out.splice(Math.max(0, Math.min(to, out.length)), 0, item)
|
|
16
|
+
return out
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const emptyGroup = () => ({ columns: 1, fields: [] })
|
|
20
|
+
export const emptyPage = () => ({ groups: [emptyGroup()] })
|
|
21
|
+
|
|
22
|
+
/** Every page with at least one group, which is what the designer edits. */
|
|
23
|
+
export function withLayout(form) {
|
|
24
|
+
const next = copy(form)
|
|
25
|
+
next.pages = next.pages?.length ? next.pages : [emptyPage()]
|
|
26
|
+
for (const page of next.pages) page.groups = page.groups?.length ? page.groups : [emptyGroup()]
|
|
27
|
+
return next
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function addPage(form) {
|
|
31
|
+
const next = copy(form)
|
|
32
|
+
next.pages.push(emptyPage())
|
|
33
|
+
return next
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function movePage(form, index, to) {
|
|
37
|
+
const next = copy(form)
|
|
38
|
+
next.pages = moved(next.pages, index, to)
|
|
39
|
+
return next
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A form keeps one page at least: the last one cannot be removed. */
|
|
43
|
+
export function removePage(form, index) {
|
|
44
|
+
if (form.pages.length <= 1) return form
|
|
45
|
+
const next = copy(form)
|
|
46
|
+
next.pages.splice(index, 1)
|
|
47
|
+
return next
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function setPageCaption(form, index, caption) {
|
|
51
|
+
const next = copy(form)
|
|
52
|
+
if (caption.trim()) next.pages[index].caption = caption
|
|
53
|
+
else delete next.pages[index].caption
|
|
54
|
+
return next
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function addGroup(form, page) {
|
|
58
|
+
const next = copy(form)
|
|
59
|
+
next.pages[page].groups.push(emptyGroup())
|
|
60
|
+
return next
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function moveGroup(form, page, index, to) {
|
|
64
|
+
const next = copy(form)
|
|
65
|
+
next.pages[page].groups = moved(next.pages[page].groups, index, to)
|
|
66
|
+
return next
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A page keeps one group at least. */
|
|
70
|
+
export function removeGroup(form, page, index) {
|
|
71
|
+
if (form.pages[page].groups.length <= 1) return form
|
|
72
|
+
const next = copy(form)
|
|
73
|
+
next.pages[page].groups.splice(index, 1)
|
|
74
|
+
return next
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function setGroupCaption(form, page, index, caption) {
|
|
78
|
+
const next = copy(form)
|
|
79
|
+
if (caption.trim()) next.pages[page].groups[index].caption = caption
|
|
80
|
+
else delete next.pages[page].groups[index].caption
|
|
81
|
+
return next
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Between 1 and 4, as the file allows. */
|
|
85
|
+
export function setColumns(form, page, index, columns) {
|
|
86
|
+
const next = copy(form)
|
|
87
|
+
next.pages[page].groups[index].columns = Math.max(1, Math.min(4, Math.round(columns) || 1))
|
|
88
|
+
return next
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Moves a field to a place, `{ page, group, index }`, in its own group or
|
|
93
|
+
* another. Within one group the index is where it ends up.
|
|
94
|
+
*/
|
|
95
|
+
export function moveField(form, from, to) {
|
|
96
|
+
const next = copy(form)
|
|
97
|
+
const source = next.pages[from.page]?.groups[from.group]?.fields
|
|
98
|
+
const target = next.pages[to.page]?.groups[to.group]?.fields
|
|
99
|
+
if (!source || !target || from.index < 0 || from.index >= source.length) return form
|
|
100
|
+
const [field] = source.splice(from.index, 1)
|
|
101
|
+
target.splice(Math.max(0, Math.min(to.index, target.length)), 0, field)
|
|
102
|
+
return next
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** One step up or down within a group, or into the neighbouring group at its edge. */
|
|
106
|
+
export function nudgeField(form, at, step) {
|
|
107
|
+
const groups = form.pages.flatMap((page, p) => page.groups.map((_, g) => ({ page: p, group: g })))
|
|
108
|
+
const here = groups.findIndex((place) => place.page === at.page && place.group === at.group)
|
|
109
|
+
const fields = form.pages[at.page].groups[at.group].fields
|
|
110
|
+
const index = at.index + step
|
|
111
|
+
if (index >= 0 && index < fields.length) return moveField(form, at, { ...at, index })
|
|
112
|
+
const neighbour = groups[here + step]
|
|
113
|
+
if (!neighbour) return form
|
|
114
|
+
const length = form.pages[neighbour.page].groups[neighbour.group].fields.length
|
|
115
|
+
return moveField(form, at, { ...neighbour, index: step < 0 ? length : 0 })
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Whether removing it would take fields with it, so the designer asks first. */
|
|
119
|
+
export const pageHasFields = (page) => page.groups.some((group) => group.fields.length > 0)
|
|
120
|
+
export const groupHasFields = (group) => group.fields.length > 0
|
|
121
|
+
|
|
122
|
+
/** The group's fields laid out in rows of `columns`, as the rendered form arranges them. */
|
|
123
|
+
export function layoutRows(group) {
|
|
124
|
+
const rows = []
|
|
125
|
+
for (let at = 0; at < group.fields.length; at += group.columns)
|
|
126
|
+
rows.push(group.fields.slice(at, at + group.columns))
|
|
127
|
+
return rows
|
|
128
|
+
}
|