@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
package/plugin/forms-overview.js
CHANGED
|
@@ -10,27 +10,45 @@
|
|
|
10
10
|
* back with the strict parser before keeping it, so a refusal here is the same
|
|
11
11
|
* refusal a hand-written file would get.
|
|
12
12
|
*/
|
|
13
|
+
// For its side effect: registers `umb-input-document`, which the shell loads lazily.
|
|
14
|
+
import '@umbraco-cms/backoffice/document'
|
|
13
15
|
import { css, html, nothing, repeat } from '@umbraco-cms/backoffice/external/lit'
|
|
14
16
|
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'
|
|
15
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
deleteForm,
|
|
19
|
+
listForms,
|
|
20
|
+
listFormTypes,
|
|
21
|
+
previewChoices,
|
|
22
|
+
readForm,
|
|
23
|
+
saveForm,
|
|
24
|
+
} from './forms-client.js'
|
|
25
|
+
import {
|
|
26
|
+
addGroup,
|
|
27
|
+
addPage,
|
|
28
|
+
groupHasFields,
|
|
29
|
+
layoutRows,
|
|
30
|
+
moveField,
|
|
31
|
+
moveGroup,
|
|
32
|
+
movePage,
|
|
33
|
+
nudgeField,
|
|
34
|
+
pageHasFields,
|
|
35
|
+
removeGroup,
|
|
36
|
+
removePage,
|
|
37
|
+
setColumns,
|
|
38
|
+
setGroupCaption,
|
|
39
|
+
setPageCaption,
|
|
40
|
+
withLayout,
|
|
41
|
+
} from './forms-layout.js'
|
|
16
42
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'singleChoice',
|
|
26
|
-
'multipleChoice',
|
|
27
|
-
'fileUpload',
|
|
28
|
-
'dataConsent',
|
|
29
|
-
'titleAndDescription',
|
|
30
|
-
'richText',
|
|
31
|
-
'hidden',
|
|
43
|
+
/** What a preview depends on, so it survives a layout change that copies the field. */
|
|
44
|
+
const sourceKey = (source) => `${source.kind}|${source.parent}`
|
|
45
|
+
|
|
46
|
+
/** Where a choice field's options come from, as an editor would put it. */
|
|
47
|
+
const CHOICE_FROM = [
|
|
48
|
+
{ kind: '', label: 'Options typed here' },
|
|
49
|
+
{ kind: 'content', label: "A page's child pages" },
|
|
50
|
+
{ kind: 'dictionary', label: "A dictionary item's children" },
|
|
32
51
|
]
|
|
33
|
-
const CHOICE_TYPES = new Set(['dropdown', 'singleChoice', 'multipleChoice'])
|
|
34
52
|
|
|
35
53
|
export default class CromboloFormsOverviewElement extends UmbLitElement {
|
|
36
54
|
static properties = {
|
|
@@ -39,6 +57,8 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
|
|
|
39
57
|
_problems: { state: true },
|
|
40
58
|
_notice: { state: true },
|
|
41
59
|
_busy: { state: true },
|
|
60
|
+
_choices: { state: true },
|
|
61
|
+
_types: { state: true },
|
|
42
62
|
}
|
|
43
63
|
|
|
44
64
|
constructor() {
|
|
@@ -48,6 +68,10 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
|
|
|
48
68
|
this._problems = []
|
|
49
69
|
this._notice = ''
|
|
50
70
|
this._busy = false
|
|
71
|
+
/** What each source currently offers, by field; shown under the field. */
|
|
72
|
+
this._choices = new Map()
|
|
73
|
+
/** Field types by name, as the server has them: built in and from bundles. */
|
|
74
|
+
this._types = new Map()
|
|
51
75
|
}
|
|
52
76
|
|
|
53
77
|
connectedCallback() {
|
|
@@ -57,7 +81,9 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
|
|
|
57
81
|
|
|
58
82
|
async #load() {
|
|
59
83
|
try {
|
|
60
|
-
|
|
84
|
+
const [forms, types] = await Promise.all([listForms(), listFormTypes()])
|
|
85
|
+
this._forms = forms
|
|
86
|
+
this._types = new Map(types.fields.map((info) => [info.type, info]))
|
|
61
87
|
} catch (error) {
|
|
62
88
|
this._forms = []
|
|
63
89
|
this._notice = error.message
|
|
@@ -68,7 +94,161 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
|
|
|
68
94
|
this._problems = []
|
|
69
95
|
this._notice = ''
|
|
70
96
|
const detail = await readForm(key)
|
|
71
|
-
this._editing =
|
|
97
|
+
this._editing = withLayout(detail.definition)
|
|
98
|
+
this._choices = new Map()
|
|
99
|
+
for (const field of this.#fields) if (field.source) this.#preview(field)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async #preview(field) {
|
|
103
|
+
const source = field.source
|
|
104
|
+
if (!source?.parent) {
|
|
105
|
+
this.#touch()
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const result = await previewChoices(source)
|
|
110
|
+
this._choices.set(sourceKey(source), result.ok ? result.body.options : [])
|
|
111
|
+
} catch {
|
|
112
|
+
this._choices.set(sourceKey(source), [])
|
|
113
|
+
}
|
|
114
|
+
this.#touch()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Clears what the old type kept, so a switched field carries no settings it no longer takes. */
|
|
118
|
+
#changeType(field, type) {
|
|
119
|
+
const before = this._types.get(field.type)
|
|
120
|
+
for (const setting of before?.builtIn ? before.settings : []) delete field[setting.key]
|
|
121
|
+
delete field.settings
|
|
122
|
+
field.type = type
|
|
123
|
+
if (!this._types.get(type)?.choices) {
|
|
124
|
+
field.values = []
|
|
125
|
+
delete field.source
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* One input per setting the type declares. A built-in type keeps them on the
|
|
131
|
+
* field; a bundle's type keeps them in the field's own settings.
|
|
132
|
+
*/
|
|
133
|
+
#settingsCell(field) {
|
|
134
|
+
const info = this._types.get(field.type)
|
|
135
|
+
if (!info || info.settings.length === 0) return nothing
|
|
136
|
+
const read = (key) => (info.builtIn ? field[key] : field.settings?.[key])
|
|
137
|
+
const write = (key, value) => {
|
|
138
|
+
if (info.builtIn) {
|
|
139
|
+
if (value === undefined) delete field[key]
|
|
140
|
+
else field[key] = value
|
|
141
|
+
} else {
|
|
142
|
+
field.settings = { ...(field.settings ?? {}) }
|
|
143
|
+
if (value === undefined) delete field.settings[key]
|
|
144
|
+
else field.settings[key] = value
|
|
145
|
+
if (Object.keys(field.settings).length === 0) delete field.settings
|
|
146
|
+
}
|
|
147
|
+
this.#touch()
|
|
148
|
+
}
|
|
149
|
+
return html`<div class="settings">
|
|
150
|
+
${info.settings.map((setting) => {
|
|
151
|
+
const value = read(setting.key)
|
|
152
|
+
const label = `${setting.label}${setting.required ? ' *' : ''}`
|
|
153
|
+
if (setting.kind === 'boolean')
|
|
154
|
+
return html`<uui-toggle
|
|
155
|
+
label=${label}
|
|
156
|
+
title=${setting.description ?? ''}
|
|
157
|
+
.checked=${value === true}
|
|
158
|
+
@change=${(event) => write(setting.key, event.target.checked || undefined)}
|
|
159
|
+
></uui-toggle>`
|
|
160
|
+
return html`<label>
|
|
161
|
+
<span>${label}</span>
|
|
162
|
+
<uui-input
|
|
163
|
+
title=${setting.description ?? ''}
|
|
164
|
+
type=${setting.kind === 'number' ? 'number' : 'text'}
|
|
165
|
+
.value=${value === undefined ? '' : setting.kind === 'list' ? value.join(', ') : String(value)}
|
|
166
|
+
@change=${(event) => {
|
|
167
|
+
const raw = event.target.value.trim()
|
|
168
|
+
if (raw === '') return write(setting.key, undefined)
|
|
169
|
+
if (setting.kind === 'number') return write(setting.key, Number(raw))
|
|
170
|
+
if (setting.kind === 'list')
|
|
171
|
+
return write(
|
|
172
|
+
setting.key,
|
|
173
|
+
raw
|
|
174
|
+
.split(',')
|
|
175
|
+
.map((item) => item.trim())
|
|
176
|
+
.filter(Boolean),
|
|
177
|
+
)
|
|
178
|
+
write(setting.key, raw)
|
|
179
|
+
}}
|
|
180
|
+
></uui-input>
|
|
181
|
+
</label>`
|
|
182
|
+
})}
|
|
183
|
+
</div>`
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#choicesCell(field) {
|
|
187
|
+
const kind = field.source?.kind ?? ''
|
|
188
|
+
const offered = field.source?.parent ? this._choices.get(sourceKey(field.source)) : undefined
|
|
189
|
+
const list =
|
|
190
|
+
kind === ''
|
|
191
|
+
? html`<uui-input
|
|
192
|
+
label="Options"
|
|
193
|
+
placeholder="One, Two, Three"
|
|
194
|
+
.value=${(field.values ?? []).join(', ')}
|
|
195
|
+
@change=${(event) => {
|
|
196
|
+
field.values = event.target.value
|
|
197
|
+
.split(',')
|
|
198
|
+
.map((value) => value.trim())
|
|
199
|
+
.filter(Boolean)
|
|
200
|
+
this.#touch()
|
|
201
|
+
}}
|
|
202
|
+
></uui-input>`
|
|
203
|
+
: kind === 'content'
|
|
204
|
+
? html`<umb-input-document
|
|
205
|
+
max="1"
|
|
206
|
+
.selection=${field.source.parent ? [field.source.parent] : []}
|
|
207
|
+
@change=${(event) => {
|
|
208
|
+
field.source = { kind, parent: event.target.selection?.[0] ?? '' }
|
|
209
|
+
this.#preview(field)
|
|
210
|
+
}}
|
|
211
|
+
></umb-input-document>`
|
|
212
|
+
: html`<uui-input
|
|
213
|
+
label="Dictionary item"
|
|
214
|
+
placeholder="The name of the parent item"
|
|
215
|
+
.value=${field.source.parent ?? ''}
|
|
216
|
+
@change=${(event) => {
|
|
217
|
+
field.source = { kind, parent: event.target.value.trim() }
|
|
218
|
+
this.#preview(field)
|
|
219
|
+
}}
|
|
220
|
+
></uui-input>`
|
|
221
|
+
return html`
|
|
222
|
+
<select
|
|
223
|
+
aria-label="Where the options come from"
|
|
224
|
+
@change=${(event) => {
|
|
225
|
+
const chosen = event.target.value
|
|
226
|
+
if (chosen === '') delete field.source
|
|
227
|
+
else {
|
|
228
|
+
field.source = { kind: chosen, parent: '' }
|
|
229
|
+
field.values = []
|
|
230
|
+
}
|
|
231
|
+
this.#touch()
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
234
|
+
${CHOICE_FROM.map(
|
|
235
|
+
(from) =>
|
|
236
|
+
html`<option value=${from.kind} ?selected=${from.kind === kind}>${from.label}</option>`,
|
|
237
|
+
)}
|
|
238
|
+
</select>
|
|
239
|
+
${list}
|
|
240
|
+
${
|
|
241
|
+
kind && offered
|
|
242
|
+
? html`<p class="offered">
|
|
243
|
+
${
|
|
244
|
+
offered.length > 0
|
|
245
|
+
? `Offers now: ${offered.map((option) => option.label).join(', ')}`
|
|
246
|
+
: 'Offers nothing yet.'
|
|
247
|
+
}
|
|
248
|
+
</p>`
|
|
249
|
+
: nothing
|
|
250
|
+
}
|
|
251
|
+
`
|
|
72
252
|
}
|
|
73
253
|
|
|
74
254
|
#new() {
|
|
@@ -86,8 +266,216 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
|
|
|
86
266
|
}
|
|
87
267
|
}
|
|
88
268
|
|
|
269
|
+
/** Every field, in order: for the previews, which a layout change does not touch. */
|
|
89
270
|
get #fields() {
|
|
90
|
-
return this._editing?.pages
|
|
271
|
+
return (this._editing?.pages ?? []).flatMap((page) =>
|
|
272
|
+
page.groups.flatMap((group) => group.fields),
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Applies a layout operation from forms-layout.js to the form being edited. */
|
|
277
|
+
#apply(operation, ...args) {
|
|
278
|
+
this._editing = operation(this._editing, ...args)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#page(page, p) {
|
|
282
|
+
const pages = this._editing.pages
|
|
283
|
+
return html`<section class="page" aria-label=${page.caption || `Page ${p + 1}`}>
|
|
284
|
+
<div class="bar">
|
|
285
|
+
<uui-input
|
|
286
|
+
label="Page title"
|
|
287
|
+
placeholder=${`Page ${p + 1}`}
|
|
288
|
+
.value=${page.caption ?? ''}
|
|
289
|
+
@change=${(event) => this.#apply(setPageCaption, p, event.target.value)}
|
|
290
|
+
></uui-input>
|
|
291
|
+
<uui-button compact look="secondary" label="Move page up" ?disabled=${p === 0}
|
|
292
|
+
@click=${() => this.#apply(movePage, p, p - 1)}>↑</uui-button>
|
|
293
|
+
<uui-button compact look="secondary" label="Move page down" ?disabled=${p === pages.length - 1}
|
|
294
|
+
@click=${() => this.#apply(movePage, p, p + 1)}>↓</uui-button>
|
|
295
|
+
<uui-button compact look="secondary" color="danger" label="Remove page" ?disabled=${pages.length === 1}
|
|
296
|
+
@click=${() => {
|
|
297
|
+
if (pageHasFields(page) && !confirm('Remove this page and the fields on it?')) return
|
|
298
|
+
this.#apply(removePage, p)
|
|
299
|
+
}}></uui-button>
|
|
300
|
+
</div>
|
|
301
|
+
${page.groups.map((group, g) => this.#group(group, p, g, page.groups.length))}
|
|
302
|
+
<uui-button look="secondary" label="Add a group" @click=${() => this.#apply(addGroup, p)}></uui-button>
|
|
303
|
+
</section>`
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#group(group, p, g, count) {
|
|
307
|
+
return html`<div
|
|
308
|
+
class="group"
|
|
309
|
+
@dragover=${(event) => event.preventDefault()}
|
|
310
|
+
@drop=${(event) => this.#drop(event, { page: p, group: g, index: group.fields.length })}
|
|
311
|
+
>
|
|
312
|
+
<div class="bar">
|
|
313
|
+
<uui-input
|
|
314
|
+
label="Group title"
|
|
315
|
+
placeholder="Untitled group"
|
|
316
|
+
.value=${group.caption ?? ''}
|
|
317
|
+
@change=${(event) => this.#apply(setGroupCaption, p, g, event.target.value)}
|
|
318
|
+
></uui-input>
|
|
319
|
+
<label class="columns">
|
|
320
|
+
Columns
|
|
321
|
+
<select @change=${(event) => this.#apply(setColumns, p, g, Number(event.target.value))}>
|
|
322
|
+
${[1, 2, 3, 4].map((n) => html`<option value=${n} ?selected=${n === group.columns}>${n}</option>`)}
|
|
323
|
+
</select>
|
|
324
|
+
</label>
|
|
325
|
+
<uui-button compact look="secondary" label="Move group up" ?disabled=${g === 0}
|
|
326
|
+
@click=${() => this.#apply(moveGroup, p, g, g - 1)}>↑</uui-button>
|
|
327
|
+
<uui-button compact look="secondary" label="Move group down" ?disabled=${g === count - 1}
|
|
328
|
+
@click=${() => this.#apply(moveGroup, p, g, g + 1)}>↓</uui-button>
|
|
329
|
+
<uui-button compact look="secondary" color="danger" label="Remove group" ?disabled=${count === 1}
|
|
330
|
+
@click=${() => {
|
|
331
|
+
if (groupHasFields(group) && !confirm('Remove this group and its fields?')) return
|
|
332
|
+
this.#apply(removeGroup, p, g)
|
|
333
|
+
}}></uui-button>
|
|
334
|
+
</div>
|
|
335
|
+
${
|
|
336
|
+
group.fields.length > 0
|
|
337
|
+
? html`<div class="preview" style=${`grid-template-columns: repeat(${group.columns}, 1fr)`} aria-hidden="true">
|
|
338
|
+
${layoutRows(group)
|
|
339
|
+
.flat()
|
|
340
|
+
.map(
|
|
341
|
+
(field) => html`<span>${field.caption || field.alias || 'Untitled field'}</span>`,
|
|
342
|
+
)}
|
|
343
|
+
</div>`
|
|
344
|
+
: nothing
|
|
345
|
+
}
|
|
346
|
+
${this.#fieldsTable(group, p, g)}
|
|
347
|
+
<uui-button
|
|
348
|
+
look="secondary"
|
|
349
|
+
label="Add a field"
|
|
350
|
+
@click=${() => {
|
|
351
|
+
group.fields.push({
|
|
352
|
+
alias: '',
|
|
353
|
+
type: 'shortAnswer',
|
|
354
|
+
caption: '',
|
|
355
|
+
mandatory: false,
|
|
356
|
+
sensitive: false,
|
|
357
|
+
values: [],
|
|
358
|
+
accept: [],
|
|
359
|
+
})
|
|
360
|
+
this.#touch()
|
|
361
|
+
}}
|
|
362
|
+
></uui-button>
|
|
363
|
+
</div>`
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
#drop(event, to) {
|
|
367
|
+
event.preventDefault()
|
|
368
|
+
event.stopPropagation()
|
|
369
|
+
const from = JSON.parse(event.dataTransfer?.getData('application/x-crombolo-field') || 'null')
|
|
370
|
+
if (from) this.#apply(moveField, from, to)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
#fieldsTable(group, p, g) {
|
|
374
|
+
return html`<uui-table>
|
|
375
|
+
<uui-table-head>
|
|
376
|
+
<uui-table-head-cell>Order</uui-table-head-cell>
|
|
377
|
+
<uui-table-head-cell>Caption</uui-table-head-cell>
|
|
378
|
+
<uui-table-head-cell>Alias</uui-table-head-cell>
|
|
379
|
+
<uui-table-head-cell>Type</uui-table-head-cell>
|
|
380
|
+
<uui-table-head-cell>Choices</uui-table-head-cell>
|
|
381
|
+
<uui-table-head-cell>Settings</uui-table-head-cell>
|
|
382
|
+
<uui-table-head-cell>Required</uui-table-head-cell>
|
|
383
|
+
<uui-table-head-cell>Sensitive</uui-table-head-cell>
|
|
384
|
+
<uui-table-head-cell></uui-table-head-cell>
|
|
385
|
+
</uui-table-head>
|
|
386
|
+
${repeat(
|
|
387
|
+
group.fields,
|
|
388
|
+
(_field, index) => index,
|
|
389
|
+
(field, index) => html`
|
|
390
|
+
<uui-table-row
|
|
391
|
+
draggable="true"
|
|
392
|
+
@dragstart=${(event) =>
|
|
393
|
+
event.dataTransfer?.setData(
|
|
394
|
+
'application/x-crombolo-field',
|
|
395
|
+
JSON.stringify({ page: p, group: g, index }),
|
|
396
|
+
)}
|
|
397
|
+
@dragover=${(event) => event.preventDefault()}
|
|
398
|
+
@drop=${(event) => this.#drop(event, { page: p, group: g, index })}
|
|
399
|
+
>
|
|
400
|
+
<uui-table-cell>
|
|
401
|
+
<uui-button compact look="secondary" label="Move field up"
|
|
402
|
+
@click=${() => this.#apply(nudgeField, { page: p, group: g, index }, -1)}>↑</uui-button>
|
|
403
|
+
<uui-button compact look="secondary" label="Move field down"
|
|
404
|
+
@click=${() => this.#apply(nudgeField, { page: p, group: g, index }, 1)}>↓</uui-button>
|
|
405
|
+
</uui-table-cell>
|
|
406
|
+
<uui-table-cell>
|
|
407
|
+
<uui-input
|
|
408
|
+
label="Caption"
|
|
409
|
+
.value=${field.caption ?? ''}
|
|
410
|
+
@change=${(event) => {
|
|
411
|
+
field.caption = event.target.value
|
|
412
|
+
this.#touch()
|
|
413
|
+
}}
|
|
414
|
+
></uui-input>
|
|
415
|
+
</uui-table-cell>
|
|
416
|
+
<uui-table-cell>
|
|
417
|
+
<uui-input
|
|
418
|
+
label="Alias"
|
|
419
|
+
.value=${field.alias ?? ''}
|
|
420
|
+
@change=${(event) => {
|
|
421
|
+
field.alias = event.target.value
|
|
422
|
+
this.#touch()
|
|
423
|
+
}}
|
|
424
|
+
></uui-input>
|
|
425
|
+
</uui-table-cell>
|
|
426
|
+
<uui-table-cell>
|
|
427
|
+
<select
|
|
428
|
+
aria-label="Type"
|
|
429
|
+
@change=${(event) => {
|
|
430
|
+
this.#changeType(field, event.target.value)
|
|
431
|
+
this.#touch()
|
|
432
|
+
}}
|
|
433
|
+
>
|
|
434
|
+
${[...this._types.values()].map(
|
|
435
|
+
(info) =>
|
|
436
|
+
html`<option value=${info.type} ?selected=${info.type === field.type}>${info.label}</option>`,
|
|
437
|
+
)}
|
|
438
|
+
</select>
|
|
439
|
+
</uui-table-cell>
|
|
440
|
+
<uui-table-cell>
|
|
441
|
+
${this._types.get(field.type)?.choices ? this.#choicesCell(field) : nothing}
|
|
442
|
+
</uui-table-cell>
|
|
443
|
+
<uui-table-cell>${this.#settingsCell(field)}</uui-table-cell>
|
|
444
|
+
<uui-table-cell>
|
|
445
|
+
<uui-toggle
|
|
446
|
+
label="Required"
|
|
447
|
+
.checked=${field.mandatory === true}
|
|
448
|
+
@change=${(event) => {
|
|
449
|
+
field.mandatory = event.target.checked
|
|
450
|
+
this.#touch()
|
|
451
|
+
}}
|
|
452
|
+
></uui-toggle>
|
|
453
|
+
</uui-table-cell>
|
|
454
|
+
<uui-table-cell>
|
|
455
|
+
<uui-toggle
|
|
456
|
+
label="Sensitive"
|
|
457
|
+
.checked=${field.sensitive === true}
|
|
458
|
+
@change=${(event) => {
|
|
459
|
+
field.sensitive = event.target.checked
|
|
460
|
+
this.#touch()
|
|
461
|
+
}}
|
|
462
|
+
></uui-toggle>
|
|
463
|
+
</uui-table-cell>
|
|
464
|
+
<uui-table-cell>
|
|
465
|
+
<uui-button
|
|
466
|
+
look="secondary"
|
|
467
|
+
color="danger"
|
|
468
|
+
label="Remove"
|
|
469
|
+
@click=${() => {
|
|
470
|
+
group.fields.splice(index, 1)
|
|
471
|
+
this.#touch()
|
|
472
|
+
}}
|
|
473
|
+
></uui-button>
|
|
474
|
+
</uui-table-cell>
|
|
475
|
+
</uui-table-row>
|
|
476
|
+
`,
|
|
477
|
+
)}
|
|
478
|
+
</uui-table>`
|
|
91
479
|
}
|
|
92
480
|
|
|
93
481
|
#touch() {
|
|
@@ -222,122 +610,29 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
|
|
|
222
610
|
}}
|
|
223
611
|
></uui-toggle>
|
|
224
612
|
</uui-form-layout-item>
|
|
613
|
+
${
|
|
614
|
+
form.pages.length > 1
|
|
615
|
+
? html`<uui-form-layout-item>
|
|
616
|
+
<uui-label slot="label">Show one page at a time</uui-label>
|
|
617
|
+
<span slot="description">Each page is checked before the next. Without JavaScript the pages show together.</span>
|
|
618
|
+
<uui-toggle
|
|
619
|
+
.checked=${form.stepPages === true}
|
|
620
|
+
@change=${(event) => {
|
|
621
|
+
if (event.target.checked) form.stepPages = true
|
|
622
|
+
else delete form.stepPages
|
|
623
|
+
this.#touch()
|
|
624
|
+
}}
|
|
625
|
+
></uui-toggle>
|
|
626
|
+
</uui-form-layout-item>
|
|
627
|
+
${text('nextLabel', 'Next button')}
|
|
628
|
+
${text('previousLabel', 'Back button')}`
|
|
629
|
+
: nothing
|
|
630
|
+
}
|
|
225
631
|
|
|
226
|
-
<h4>
|
|
227
|
-
|
|
228
|
-
<uui-table-head>
|
|
229
|
-
<uui-table-head-cell>Caption</uui-table-head-cell>
|
|
230
|
-
<uui-table-head-cell>Alias</uui-table-head-cell>
|
|
231
|
-
<uui-table-head-cell>Type</uui-table-head-cell>
|
|
232
|
-
<uui-table-head-cell>Choices</uui-table-head-cell>
|
|
233
|
-
<uui-table-head-cell>Required</uui-table-head-cell>
|
|
234
|
-
<uui-table-head-cell>Sensitive</uui-table-head-cell>
|
|
235
|
-
<uui-table-head-cell></uui-table-head-cell>
|
|
236
|
-
</uui-table-head>
|
|
237
|
-
${repeat(
|
|
238
|
-
this.#fields,
|
|
239
|
-
(_field, index) => index,
|
|
240
|
-
(field, index) => html`
|
|
241
|
-
<uui-table-row>
|
|
242
|
-
<uui-table-cell>
|
|
243
|
-
<uui-input
|
|
244
|
-
.value=${field.caption ?? ''}
|
|
245
|
-
@change=${(event) => {
|
|
246
|
-
field.caption = event.target.value
|
|
247
|
-
this.#touch()
|
|
248
|
-
}}
|
|
249
|
-
></uui-input>
|
|
250
|
-
</uui-table-cell>
|
|
251
|
-
<uui-table-cell>
|
|
252
|
-
<uui-input
|
|
253
|
-
.value=${field.alias ?? ''}
|
|
254
|
-
@change=${(event) => {
|
|
255
|
-
field.alias = event.target.value
|
|
256
|
-
this.#touch()
|
|
257
|
-
}}
|
|
258
|
-
></uui-input>
|
|
259
|
-
</uui-table-cell>
|
|
260
|
-
<uui-table-cell>
|
|
261
|
-
<select
|
|
262
|
-
@change=${(event) => {
|
|
263
|
-
field.type = event.target.value
|
|
264
|
-
if (!CHOICE_TYPES.has(field.type)) field.values = []
|
|
265
|
-
this.#touch()
|
|
266
|
-
}}
|
|
267
|
-
>
|
|
268
|
-
${FIELD_TYPES.map(
|
|
269
|
-
(type) =>
|
|
270
|
-
html`<option value=${type} ?selected=${type === field.type}>${type}</option>`,
|
|
271
|
-
)}
|
|
272
|
-
</select>
|
|
273
|
-
</uui-table-cell>
|
|
274
|
-
<uui-table-cell>
|
|
275
|
-
${
|
|
276
|
-
CHOICE_TYPES.has(field.type)
|
|
277
|
-
? html`<uui-input
|
|
278
|
-
placeholder="One, Two, Three"
|
|
279
|
-
.value=${(field.values ?? []).join(', ')}
|
|
280
|
-
@change=${(event) => {
|
|
281
|
-
field.values = event.target.value
|
|
282
|
-
.split(',')
|
|
283
|
-
.map((value) => value.trim())
|
|
284
|
-
.filter(Boolean)
|
|
285
|
-
this.#touch()
|
|
286
|
-
}}
|
|
287
|
-
></uui-input>`
|
|
288
|
-
: nothing
|
|
289
|
-
}
|
|
290
|
-
</uui-table-cell>
|
|
291
|
-
<uui-table-cell>
|
|
292
|
-
<uui-toggle
|
|
293
|
-
.checked=${field.mandatory === true}
|
|
294
|
-
@change=${(event) => {
|
|
295
|
-
field.mandatory = event.target.checked
|
|
296
|
-
this.#touch()
|
|
297
|
-
}}
|
|
298
|
-
></uui-toggle>
|
|
299
|
-
</uui-table-cell>
|
|
300
|
-
<uui-table-cell>
|
|
301
|
-
<uui-toggle
|
|
302
|
-
.checked=${field.sensitive === true}
|
|
303
|
-
@change=${(event) => {
|
|
304
|
-
field.sensitive = event.target.checked
|
|
305
|
-
this.#touch()
|
|
306
|
-
}}
|
|
307
|
-
></uui-toggle>
|
|
308
|
-
</uui-table-cell>
|
|
309
|
-
<uui-table-cell>
|
|
310
|
-
<uui-button
|
|
311
|
-
look="secondary"
|
|
312
|
-
color="danger"
|
|
313
|
-
label="Remove"
|
|
314
|
-
@click=${() => {
|
|
315
|
-
this.#fields.splice(index, 1)
|
|
316
|
-
this.#touch()
|
|
317
|
-
}}
|
|
318
|
-
></uui-button>
|
|
319
|
-
</uui-table-cell>
|
|
320
|
-
</uui-table-row>
|
|
321
|
-
`,
|
|
322
|
-
)}
|
|
323
|
-
</uui-table>
|
|
632
|
+
<h4>Layout</h4>
|
|
633
|
+
${this._editing.pages.map((page, p) => this.#page(page, p))}
|
|
324
634
|
<div class="actions">
|
|
325
|
-
<uui-button
|
|
326
|
-
look="secondary"
|
|
327
|
-
label="Add a field"
|
|
328
|
-
@click=${() => {
|
|
329
|
-
this.#fields.push({
|
|
330
|
-
alias: '',
|
|
331
|
-
type: 'shortAnswer',
|
|
332
|
-
caption: '',
|
|
333
|
-
mandatory: false,
|
|
334
|
-
sensitive: false,
|
|
335
|
-
values: [],
|
|
336
|
-
accept: [],
|
|
337
|
-
})
|
|
338
|
-
this.#touch()
|
|
339
|
-
}}
|
|
340
|
-
></uui-button>
|
|
635
|
+
<uui-button look="secondary" label="Add a page" @click=${() => this.#apply(addPage)}></uui-button>
|
|
341
636
|
<uui-button look="primary" label="Save" ?disabled=${this._busy} @click=${() => this.#save()}></uui-button>
|
|
342
637
|
<uui-button look="secondary" label="Cancel" @click=${() => {
|
|
343
638
|
this._editing = undefined
|
|
@@ -349,6 +644,61 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
|
|
|
349
644
|
}
|
|
350
645
|
|
|
351
646
|
static styles = css`
|
|
647
|
+
.page {
|
|
648
|
+
border: 1px solid var(--uui-color-border, #ddd);
|
|
649
|
+
border-radius: var(--uui-border-radius, 3px);
|
|
650
|
+
padding: var(--uui-size-space-4, 12px);
|
|
651
|
+
margin-bottom: var(--uui-size-space-4, 12px);
|
|
652
|
+
}
|
|
653
|
+
.group {
|
|
654
|
+
border-left: 3px solid var(--uui-color-divider-emphasis, #ccc);
|
|
655
|
+
padding: var(--uui-size-space-3, 9px) 0 var(--uui-size-space-3, 9px)
|
|
656
|
+
var(--uui-size-space-4, 12px);
|
|
657
|
+
margin: var(--uui-size-space-3, 9px) 0;
|
|
658
|
+
}
|
|
659
|
+
.bar {
|
|
660
|
+
display: flex;
|
|
661
|
+
flex-wrap: wrap;
|
|
662
|
+
align-items: center;
|
|
663
|
+
gap: var(--uui-size-space-2, 6px);
|
|
664
|
+
margin-bottom: var(--uui-size-space-3, 9px);
|
|
665
|
+
}
|
|
666
|
+
.columns {
|
|
667
|
+
display: inline-flex;
|
|
668
|
+
align-items: center;
|
|
669
|
+
gap: var(--uui-size-space-2, 6px);
|
|
670
|
+
}
|
|
671
|
+
.preview {
|
|
672
|
+
display: grid;
|
|
673
|
+
gap: var(--uui-size-space-2, 6px);
|
|
674
|
+
margin-bottom: var(--uui-size-space-3, 9px);
|
|
675
|
+
}
|
|
676
|
+
.preview span {
|
|
677
|
+
padding: var(--uui-size-space-2, 6px);
|
|
678
|
+
background: var(--uui-color-surface-alt, #f3f3f5);
|
|
679
|
+
border-radius: var(--uui-border-radius, 3px);
|
|
680
|
+
font-size: 0.9em;
|
|
681
|
+
overflow: hidden;
|
|
682
|
+
text-overflow: ellipsis;
|
|
683
|
+
white-space: nowrap;
|
|
684
|
+
}
|
|
685
|
+
uui-table-row[draggable='true'] {
|
|
686
|
+
cursor: grab;
|
|
687
|
+
}
|
|
688
|
+
.settings {
|
|
689
|
+
display: grid;
|
|
690
|
+
gap: var(--uui-size-space-2, 6px);
|
|
691
|
+
}
|
|
692
|
+
.settings label {
|
|
693
|
+
display: grid;
|
|
694
|
+
gap: 2px;
|
|
695
|
+
font-size: 0.9em;
|
|
696
|
+
}
|
|
697
|
+
.offered {
|
|
698
|
+
margin: var(--uui-size-space-2, 6px) 0 0;
|
|
699
|
+
color: var(--uui-color-text-alt, #555);
|
|
700
|
+
font-size: 0.9em;
|
|
701
|
+
}
|
|
352
702
|
:host {
|
|
353
703
|
display: block;
|
|
354
704
|
padding: var(--uui-size-layout-1, 24px);
|