@crombolo/backoffice-host 0.13.1 → 0.14.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/assets/login.js +89 -2
- package/package.json +2 -2
- package/plugin/assistant/umbraco-package.json +1 -1
- package/plugin/branding/branding.js +9 -14
- package/plugin/branding/bundle-builder-labels.js +30 -1
- package/plugin/branding/localization-en.js +8 -0
- package/plugin/branding/sign-in.js +31 -0
- package/plugin/branding/theme.css +2 -0
- package/plugin/bundles-created-overview.js +31 -18
- package/plugin/bundles-installed.js +127 -61
- package/plugin/bundles-marketplace.js +1 -1
- package/plugin/changes-dashboard.js +8 -1
- package/plugin/download.js +19 -0
- package/plugin/form-picker.js +1 -1
- package/plugin/forms-client.js +5 -0
- package/plugin/forms-entries.js +30 -16
- package/plugin/forms-overview.js +55 -33
- package/plugin/localizations/ar.js +3 -39
- package/plugin/localizations/bs.js +3 -29
- package/plugin/localizations/cs.js +3 -24
- package/plugin/localizations/cy.js +3 -32
- package/plugin/localizations/da.js +3 -41
- package/plugin/localizations/de.js +3 -27
- package/plugin/localizations/es.js +3 -19
- package/plugin/localizations/fr.js +3 -26
- package/plugin/localizations/hr.js +3 -29
- package/plugin/localizations/it.js +3 -29
- package/plugin/localizations/ja.js +3 -11
- package/plugin/localizations/ko.js +3 -11
- package/plugin/localizations/nb.js +3 -18
- package/plugin/localizations/nl.js +3 -29
- package/plugin/localizations/pl.js +3 -11
- package/plugin/localizations/pt-br.js +0 -10
- package/plugin/localizations/pt.js +3 -39
- package/plugin/localizations/ru.js +3 -9
- package/plugin/localizations/sv.js +3 -20
- package/plugin/localizations/tr.js +3 -26
- package/plugin/localizations/uk.js +3 -9
- package/plugin/localizations/umbraco-package.json +22 -2
- package/plugin/localizations/vi.js +3 -42
- package/plugin/localizations/zh-tw.js +0 -6
- package/plugin/localizations/zh.js +0 -6
- package/plugin/seo/umbraco-package.json +1 -1
- package/plugin/sign-in/entry.js +20 -0
- package/plugin/sign-in/sign-in-dialog.js +127 -0
- package/plugin/sign-in/umbraco-package.json +15 -0
- package/plugin/two-factor-setup.js +147 -0
- package/plugin/umbraco-package.json +30 -30
- package/plugin/welcome-dashboard.js +2 -5
- package/plugin/workflow-designer.js +298 -187
- package/plugin/workflow-requests.js +19 -6
- package/src/shell.ts +4 -1
- package/plugin/branding/sysinfo.js +0 -89
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The workflow designer (story 028): stages drawn on a canvas and connected by
|
|
3
3
|
* arrows, each stage's approvers and send-back beside it, and the workflow's
|
|
4
|
-
* own settings
|
|
4
|
+
* own settings on a tab of their own. Saving writes `schema/workflows/<alias>.toml`; before it
|
|
5
5
|
* does, the requests in flight that the change affects are named.
|
|
6
6
|
*
|
|
7
7
|
* An arrow that would close a loop is refused as it is drawn. The server holds
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
// For its side effect: registers `umb-input-document`, which the shell loads lazily.
|
|
11
11
|
import '@umbraco-cms/backoffice/document'
|
|
12
|
-
import { css, html,
|
|
12
|
+
import { css, html, nothing, unsafeCSS } from '@umbraco-cms/backoffice/external/lit'
|
|
13
|
+
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'
|
|
14
|
+
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'
|
|
13
15
|
import { deleteWorkflow, listWorkflows, previewWorkflow, saveWorkflow } from './workflow-client.js'
|
|
14
16
|
import { ancestors, BOX, drawGraph, GRAPH_STYLES, layout, wouldLoop } from './workflow-graph.js'
|
|
15
17
|
|
|
@@ -58,15 +60,26 @@ const blank = () => ({
|
|
|
58
60
|
],
|
|
59
61
|
})
|
|
60
62
|
|
|
61
|
-
const select = (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
</select>`
|
|
63
|
+
const select = (label, value, options, onChange, disabled) => html`<uui-select
|
|
64
|
+
label=${label}
|
|
65
|
+
?disabled=${disabled}
|
|
66
|
+
.options=${Object.entries(options).map(([key, name]) => ({ name, value: key, selected: key === value }))}
|
|
67
|
+
@change=${(e) => onChange(e.target.value)}
|
|
68
|
+
></uui-select>`
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
/** A labelled row, as every backoffice editor lays out a field. */
|
|
71
|
+
const property = (
|
|
72
|
+
label,
|
|
73
|
+
description,
|
|
74
|
+
editor,
|
|
75
|
+
orientation = 'horizontal',
|
|
76
|
+
) => html`<umb-property-layout
|
|
77
|
+
orientation=${orientation}
|
|
78
|
+
label=${label}
|
|
79
|
+
description=${description ?? ''}
|
|
80
|
+
><div slot="editor">${editor}</div></umb-property-layout>`
|
|
81
|
+
|
|
82
|
+
export default class CromboloWorkflowDesignerElement extends UmbLitElement {
|
|
70
83
|
static properties = {
|
|
71
84
|
_list: { state: true },
|
|
72
85
|
_editing: { state: true },
|
|
@@ -76,9 +89,18 @@ export default class CromboloWorkflowDesignerElement extends LitElement {
|
|
|
76
89
|
_problems: { state: true },
|
|
77
90
|
_affected: { state: true },
|
|
78
91
|
_message: { state: true },
|
|
92
|
+
_tab: { state: true },
|
|
79
93
|
}
|
|
80
94
|
|
|
81
95
|
#drag
|
|
96
|
+
#notifications
|
|
97
|
+
|
|
98
|
+
constructor() {
|
|
99
|
+
super()
|
|
100
|
+
this.consumeContext(UMB_NOTIFICATION_CONTEXT, (context) => {
|
|
101
|
+
this.#notifications = context
|
|
102
|
+
})
|
|
103
|
+
}
|
|
82
104
|
|
|
83
105
|
connectedCallback() {
|
|
84
106
|
super.connectedCallback()
|
|
@@ -105,6 +127,7 @@ export default class CromboloWorkflowDesignerElement extends LitElement {
|
|
|
105
127
|
this._problems = undefined
|
|
106
128
|
this._affected = undefined
|
|
107
129
|
this._message = undefined
|
|
130
|
+
this._tab = 'settings'
|
|
108
131
|
}
|
|
109
132
|
|
|
110
133
|
#touch() {
|
|
@@ -247,19 +270,21 @@ export default class CromboloWorkflowDesignerElement extends LitElement {
|
|
|
247
270
|
return
|
|
248
271
|
}
|
|
249
272
|
this._affected = undefined
|
|
250
|
-
|
|
273
|
+
// The green toast every workspace shows on a save.
|
|
274
|
+
this.#notifications?.peek('positive', { data: { message: 'Workflow saved' } })
|
|
251
275
|
this._originalAlias = this._editing.alias
|
|
252
276
|
await this.#load()
|
|
253
277
|
}
|
|
254
278
|
|
|
255
|
-
|
|
279
|
+
/** From the list or the editor: the workflow by its name and the alias its file has. */
|
|
280
|
+
async #delete(name, alias) {
|
|
256
281
|
if (
|
|
257
282
|
!window.confirm(
|
|
258
|
-
`Delete the workflow "${
|
|
283
|
+
`Delete the workflow "${name}"? Pages it covers will publish without approval.`,
|
|
259
284
|
)
|
|
260
285
|
)
|
|
261
286
|
return
|
|
262
|
-
const result = await deleteWorkflow(
|
|
287
|
+
const result = await deleteWorkflow(alias)
|
|
263
288
|
if (!result.ok) {
|
|
264
289
|
this._message = result.message
|
|
265
290
|
return
|
|
@@ -270,15 +295,24 @@ export default class CromboloWorkflowDesignerElement extends LitElement {
|
|
|
270
295
|
|
|
271
296
|
render() {
|
|
272
297
|
if (this._message && !this._list)
|
|
273
|
-
return html`<uui-box headline="Workflows">${this._message}</uui-box>`
|
|
274
|
-
if (!this._list)
|
|
298
|
+
return html`<div class="list"><uui-box headline="Workflows">${this._message}</uui-box></div>`
|
|
299
|
+
if (!this._list)
|
|
300
|
+
return html`<div class="list"><uui-box headline="Workflows"><uui-loader></uui-loader></uui-box></div>`
|
|
275
301
|
if (!this._editing) return this.#renderList()
|
|
276
302
|
return this.#renderEditor()
|
|
277
303
|
}
|
|
278
304
|
|
|
279
305
|
#renderList() {
|
|
280
306
|
const { items, editable } = this._list
|
|
281
|
-
|
|
307
|
+
// The create button above the list, as the Created bundles view has it.
|
|
308
|
+
return html`<div class="list">
|
|
309
|
+
${
|
|
310
|
+
editable
|
|
311
|
+
? html`<uui-button class="create" color="default" look="outline" label="Create" @click=${() => this.#edit(blank())}>Create</uui-button>`
|
|
312
|
+
: nothing
|
|
313
|
+
}
|
|
314
|
+
${this._message ? html`<uui-box><p class="message">${this._message}</p></uui-box>` : nothing}
|
|
315
|
+
<uui-box headline="Workflows">
|
|
282
316
|
${editable ? nothing : html`<p>Workflows are kept with the site's files, which cannot be changed on this server. Change them in the repository and deploy.</p>`}
|
|
283
317
|
${
|
|
284
318
|
items.length === 0
|
|
@@ -288,13 +322,11 @@ export default class CromboloWorkflowDesignerElement extends LitElement {
|
|
|
288
322
|
<uui-table-head-cell>Workflow</uui-table-head-cell>
|
|
289
323
|
<uui-table-head-cell>Covers</uui-table-head-cell>
|
|
290
324
|
<uui-table-head-cell>Stages</uui-table-head-cell>
|
|
325
|
+
<uui-table-head-cell class="actions"></uui-table-head-cell>
|
|
291
326
|
</uui-table-head>
|
|
292
327
|
${items.map(
|
|
293
328
|
(w) => html`<uui-table-row>
|
|
294
|
-
<uui-table-cell
|
|
295
|
-
e.preventDefault()
|
|
296
|
-
this.#edit(w)
|
|
297
|
-
}}>${w.name}</a></uui-table-cell>
|
|
329
|
+
<uui-table-cell>${w.name}</uui-table-cell>
|
|
298
330
|
<uui-table-cell>${[
|
|
299
331
|
w.appliesTo.branches.length ? `${w.appliesTo.branches.length} branch(es)` : '',
|
|
300
332
|
...w.appliesTo.contentTypes,
|
|
@@ -302,55 +334,92 @@ export default class CromboloWorkflowDesignerElement extends LitElement {
|
|
|
302
334
|
.filter(Boolean)
|
|
303
335
|
.join(', ')}</uui-table-cell>
|
|
304
336
|
<uui-table-cell>${w.stages.length}</uui-table-cell>
|
|
337
|
+
<uui-table-cell class="actions">
|
|
338
|
+
<uui-button look="secondary" label=${editable ? 'Edit' : 'View'} @click=${() => this.#edit(w)}></uui-button>
|
|
339
|
+
${
|
|
340
|
+
editable
|
|
341
|
+
? html`<uui-button look="secondary" color="danger" label="Delete" @click=${() => this.#delete(w.name, w.alias)}></uui-button>`
|
|
342
|
+
: nothing
|
|
343
|
+
}
|
|
344
|
+
</uui-table-cell>
|
|
305
345
|
</uui-table-row>`,
|
|
306
346
|
)}
|
|
307
347
|
</uui-table>`
|
|
308
348
|
}
|
|
309
|
-
|
|
310
|
-
</
|
|
349
|
+
</uui-box>
|
|
350
|
+
</div>`
|
|
311
351
|
}
|
|
312
352
|
|
|
313
353
|
#renderEditor() {
|
|
314
354
|
const w = this._editing
|
|
315
355
|
const editable = this._list.editable
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
${this._problems ? html`<ul class="problems">${this._problems.map((p) => html`<li>${p.path ? html`<code>${p.path}</code> ` : nothing}${p.message}</li>`)}</ul>` : nothing}
|
|
345
|
-
${
|
|
346
|
-
this._affected
|
|
347
|
-
? html`<div class="affected">
|
|
356
|
+
const leave = () => {
|
|
357
|
+
this._editing = undefined
|
|
358
|
+
this._message = undefined
|
|
359
|
+
this._problems = undefined
|
|
360
|
+
}
|
|
361
|
+
const tab = (id, label, icon) => html`<uui-tab
|
|
362
|
+
label=${label}
|
|
363
|
+
?active=${this._tab === id}
|
|
364
|
+
@click=${() => {
|
|
365
|
+
this._tab = id
|
|
366
|
+
}}
|
|
367
|
+
><umb-icon slot="icon" name=${icon}></umb-icon>${label}</uui-tab>`
|
|
368
|
+
// Umbraco's own layout, so Save sits bottom right in the footer as it does
|
|
369
|
+
// in every workspace, with Back beside it, and the tabs top right.
|
|
370
|
+
return html`<umb-body-layout headline=${w.name || 'Workflow'}>
|
|
371
|
+
<uui-tab-group slot="navigation">
|
|
372
|
+
${tab('settings', 'Settings', 'icon-settings')}
|
|
373
|
+
${tab('stages', 'Stages', 'icon-mindmap')}
|
|
374
|
+
</uui-tab-group>
|
|
375
|
+
${this._message ? html`<uui-box class="notice"><p class="message">${this._message}</p></uui-box>` : nothing}
|
|
376
|
+
${
|
|
377
|
+
this._problems
|
|
378
|
+
? html`<uui-box class="notice" headline="Not saved"><ul class="problems">${this._problems.map((p) => html`<li>${p.path ? html`<code>${p.path}</code> ` : nothing}${p.message}</li>`)}</ul></uui-box>`
|
|
379
|
+
: nothing
|
|
380
|
+
}
|
|
381
|
+
${
|
|
382
|
+
this._affected
|
|
383
|
+
? html`<uui-box class="notice affected" headline="Pages waiting for approval">
|
|
348
384
|
<p>Saving changes the rules for ${this._affected.length} page(s) waiting for approval:</p>
|
|
349
385
|
<ul>${this._affected.map((a) => html`<li>${a.page}: ${ON_CHANGE[a.outcome]}</li>`)}</ul>
|
|
350
386
|
<uui-button look="primary" label="Save anyway" @click=${() => this.#save(true)}>Save anyway</uui-button>
|
|
387
|
+
</uui-box>`
|
|
388
|
+
: nothing
|
|
389
|
+
}
|
|
390
|
+
${this._tab === 'stages' ? this.#renderStages(w, editable) : this.#renderSettings(w, editable)}
|
|
391
|
+
${
|
|
392
|
+
this._problems
|
|
393
|
+
? html`<span slot="footer-info" class="not-saved">Not saved: ${this._problems.length} problem(s), listed above</span>`
|
|
394
|
+
: nothing
|
|
395
|
+
}
|
|
396
|
+
<uui-button slot="actions" look="secondary" label="Back to the list" @click=${leave}>Back to the list</uui-button>
|
|
397
|
+
${
|
|
398
|
+
editable
|
|
399
|
+
? html`<uui-button slot="actions" look="primary" color="positive" label="Save" @click=${() => this.#save()}>Save</uui-button>`
|
|
400
|
+
: nothing
|
|
401
|
+
}
|
|
402
|
+
</umb-body-layout>`
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
#renderStages(w, editable) {
|
|
406
|
+
const stage = this.#stage(this._selected)
|
|
407
|
+
// One box, two columns: the graph, and the stage selected in it.
|
|
408
|
+
return html`<uui-box headline="Stages">
|
|
409
|
+
${
|
|
410
|
+
editable
|
|
411
|
+
? html`<div slot="header-actions" class="toolbar">
|
|
412
|
+
<uui-button look="secondary" compact label="Add a stage" @click=${() => this.#addStage()}>Add a stage</uui-button>
|
|
413
|
+
<uui-button look=${this._connecting ? 'primary' : 'secondary'} compact label="Connect stages"
|
|
414
|
+
@click=${() => {
|
|
415
|
+
this._connecting = this._connecting ? undefined : true
|
|
416
|
+
}}
|
|
417
|
+
>${this._connecting === true ? 'Click the stage to draw from' : this._connecting ? 'Now click the stage it leads to' : 'Connect stages'}</uui-button>
|
|
351
418
|
</div>`
|
|
352
419
|
: nothing
|
|
353
420
|
}
|
|
421
|
+
<div class="stages">
|
|
422
|
+
<div class="graph">
|
|
354
423
|
<p class="hint">Arrows run left to right: the stages an arrow leads to wait for every stage with an arrow into them, and run side by side. Click an arrow to remove it.</p>
|
|
355
424
|
<div class="canvas" @pointermove=${(e) => editable && this.#pointerMove(e)} @pointerup=${() => this.#pointerUp()} @pointerleave=${() => this.#pointerUp()}>
|
|
356
425
|
${drawGraph(w.stages, {
|
|
@@ -360,168 +429,210 @@ export default class CromboloWorkflowDesignerElement extends LitElement {
|
|
|
360
429
|
onPointerDown: (alias, event) => editable && this.#pointerDown(alias, event),
|
|
361
430
|
})}
|
|
362
431
|
</div>
|
|
363
|
-
</uui-box>
|
|
364
|
-
<div class="panels">
|
|
365
|
-
${stage ? this.#renderStage(stage, editable) : nothing}
|
|
366
|
-
${this.#renderSettings(w, editable)}
|
|
367
432
|
</div>
|
|
368
|
-
|
|
433
|
+
<div class="stage">
|
|
434
|
+
${stage ? this.#renderStage(stage, editable) : html`<p class="hint">Select a stage to change it.</p>`}
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
</uui-box>`
|
|
369
438
|
}
|
|
370
439
|
|
|
371
440
|
#renderStage(stage, editable) {
|
|
372
441
|
const earlier = [...ancestors(this._editing.stages, stage.alias)]
|
|
373
442
|
const requiredKind = typeof stage.required === 'number' ? 'number' : stage.required
|
|
374
|
-
return html`<
|
|
375
|
-
|
|
376
|
-
|
|
443
|
+
return html`<div class="stage-header">
|
|
444
|
+
<h4>${stage.name || stage.alias}</h4>
|
|
445
|
+
${
|
|
446
|
+
editable && this._editing.stages.length > 1
|
|
447
|
+
? html`<uui-button look="secondary" color="danger" compact label="Remove this stage" @click=${() => this.#removeStage(stage.alias)}>Remove</uui-button>`
|
|
448
|
+
: nothing
|
|
449
|
+
}
|
|
450
|
+
</div>
|
|
451
|
+
${property(
|
|
452
|
+
'Name',
|
|
453
|
+
undefined,
|
|
454
|
+
html`<uui-input label="Name" ?readonly=${!editable} .value=${stage.name ?? ''} @change=${(
|
|
455
|
+
e,
|
|
456
|
+
) => {
|
|
377
457
|
stage.name = e.target.value
|
|
378
458
|
this.#touch()
|
|
379
|
-
}}></uui-input
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
459
|
+
}}></uui-input>`,
|
|
460
|
+
'vertical',
|
|
461
|
+
)}
|
|
462
|
+
${property(
|
|
463
|
+
'Alias',
|
|
464
|
+
'How the stage is named in the file and in other stages',
|
|
465
|
+
html`<uui-input label="Alias" ?readonly=${!editable} .value=${stage.alias} @change=${(e) => this.#renameStage(stage, e.target.value)}></uui-input>`,
|
|
466
|
+
'vertical',
|
|
467
|
+
)}
|
|
468
|
+
${property(
|
|
469
|
+
'Approved by',
|
|
470
|
+
'The user groups whose members decide this stage',
|
|
471
|
+
html`<div class="checks">${this._list.groups.map(
|
|
472
|
+
(g) =>
|
|
473
|
+
html`<uui-checkbox label=${g.name} ?disabled=${!editable} ?checked=${stage.approvers.includes(g.alias)} @change=${(
|
|
474
|
+
e,
|
|
475
|
+
) => {
|
|
476
|
+
stage.approvers = e.target.checked
|
|
477
|
+
? [...stage.approvers, g.alias]
|
|
478
|
+
: stage.approvers.filter((a) => a !== g.alias)
|
|
479
|
+
this.#touch()
|
|
480
|
+
}}>${g.name}</uui-checkbox>`,
|
|
481
|
+
)}</div>`,
|
|
482
|
+
'vertical',
|
|
483
|
+
)}
|
|
484
|
+
${property(
|
|
485
|
+
'How many must approve',
|
|
486
|
+
undefined,
|
|
487
|
+
html`${select(
|
|
488
|
+
'How many must approve',
|
|
489
|
+
requiredKind,
|
|
490
|
+
{
|
|
491
|
+
one: 'Any one of them',
|
|
492
|
+
majority: 'A majority',
|
|
493
|
+
all: 'All of them',
|
|
494
|
+
number: 'A number of them',
|
|
495
|
+
},
|
|
496
|
+
(value) => {
|
|
497
|
+
stage.required = value === 'number' ? 2 : value
|
|
498
|
+
this.#touch()
|
|
499
|
+
},
|
|
500
|
+
!editable,
|
|
501
|
+
)}
|
|
502
|
+
${
|
|
503
|
+
typeof stage.required === 'number'
|
|
504
|
+
? html`<uui-input class="count" type="number" min="1" label="How many" ?readonly=${!editable} .value=${String(stage.required)} @change=${(
|
|
385
505
|
e,
|
|
386
506
|
) => {
|
|
387
|
-
stage.
|
|
388
|
-
? [...stage.approvers, g.alias]
|
|
389
|
-
: stage.approvers.filter((a) => a !== g.alias)
|
|
507
|
+
stage.required = Math.max(1, Number(e.target.value) || 1)
|
|
390
508
|
this.#touch()
|
|
391
|
-
}}
|
|
392
|
-
)}
|
|
393
|
-
</div>
|
|
394
|
-
<label>How many must approve
|
|
395
|
-
${select(
|
|
396
|
-
requiredKind,
|
|
397
|
-
{
|
|
398
|
-
one: 'Any one of them',
|
|
399
|
-
majority: 'A majority',
|
|
400
|
-
all: 'All of them',
|
|
401
|
-
number: 'A number of them',
|
|
402
|
-
},
|
|
403
|
-
(value) => {
|
|
404
|
-
stage.required = value === 'number' ? 2 : value
|
|
405
|
-
this.#touch()
|
|
406
|
-
},
|
|
407
|
-
)}
|
|
408
|
-
${
|
|
409
|
-
typeof stage.required === 'number'
|
|
410
|
-
? html`<input type="number" min="1" .value=${String(stage.required)} @change=${(
|
|
411
|
-
e,
|
|
412
|
-
) => {
|
|
413
|
-
stage.required = Math.max(1, Number(e.target.value) || 1)
|
|
414
|
-
this.#touch()
|
|
415
|
-
}} />`
|
|
416
|
-
: nothing
|
|
417
|
-
}
|
|
418
|
-
</label>
|
|
419
|
-
<label>Sending back goes to
|
|
420
|
-
${select(
|
|
421
|
-
stage.sendBack,
|
|
422
|
-
{
|
|
423
|
-
editor: 'The editor (the request ends)',
|
|
424
|
-
...Object.fromEntries(earlier.map((a) => [a, `The ${a} stage`])),
|
|
425
|
-
},
|
|
426
|
-
(value) => {
|
|
427
|
-
stage.sendBack = value
|
|
428
|
-
this.#touch()
|
|
429
|
-
},
|
|
430
|
-
)}
|
|
431
|
-
</label>
|
|
432
|
-
${
|
|
433
|
-
editable && this._editing.stages.length > 1
|
|
434
|
-
? html`<uui-button look="secondary" color="danger" label="Remove stage" @click=${() => this.#removeStage(stage.alias)}>Remove this stage</uui-button>`
|
|
509
|
+
}}></uui-input>`
|
|
435
510
|
: nothing
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
511
|
+
}`,
|
|
512
|
+
'vertical',
|
|
513
|
+
)}
|
|
514
|
+
${property(
|
|
515
|
+
'Sending back goes to',
|
|
516
|
+
undefined,
|
|
517
|
+
select(
|
|
518
|
+
'Sending back goes to',
|
|
519
|
+
stage.sendBack,
|
|
520
|
+
{
|
|
521
|
+
editor: 'The editor (the request ends)',
|
|
522
|
+
...Object.fromEntries(
|
|
523
|
+
earlier.map((a) => [a, `The ${this.#stage(a)?.name || a} stage`]),
|
|
524
|
+
),
|
|
525
|
+
},
|
|
526
|
+
(value) => {
|
|
527
|
+
stage.sendBack = value
|
|
528
|
+
this.#touch()
|
|
529
|
+
},
|
|
530
|
+
!editable,
|
|
531
|
+
),
|
|
532
|
+
'vertical',
|
|
533
|
+
)}`
|
|
439
534
|
}
|
|
440
535
|
|
|
441
536
|
#renderSettings(w, editable) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
537
|
+
const set = (key) => (value) => {
|
|
538
|
+
w[key] = value
|
|
539
|
+
this.#touch()
|
|
540
|
+
}
|
|
541
|
+
return html`<div class="settings">
|
|
542
|
+
${editable ? nothing : html`<uui-box class="notice"><p>Workflows are kept with the site's files, which cannot be changed on this server.</p></uui-box>`}
|
|
543
|
+
<uui-box headline="Workflow">
|
|
544
|
+
${property(
|
|
545
|
+
'Name',
|
|
546
|
+
undefined,
|
|
547
|
+
html`<uui-input label="Name" ?readonly=${!editable} .value=${w.name} @change=${(e) => set('name')(e.target.value)}></uui-input>`,
|
|
548
|
+
)}
|
|
549
|
+
${property(
|
|
550
|
+
'Alias',
|
|
551
|
+
'The name of its file',
|
|
552
|
+
html`<uui-input label="Alias" ?readonly=${!editable} .value=${w.alias} @change=${(e) => set('alias')(e.target.value.trim())}></uui-input>`,
|
|
553
|
+
)}
|
|
554
|
+
${property(
|
|
555
|
+
'Covers the pages under',
|
|
556
|
+
'Each page and everything below it',
|
|
557
|
+
html`<umb-input-document
|
|
558
|
+
?readonly=${!editable}
|
|
454
559
|
.selection=${w.appliesTo.branches}
|
|
455
560
|
@change=${(e) => {
|
|
456
561
|
w.appliesTo.branches = [...(e.target.selection ?? [])]
|
|
457
562
|
this.#touch()
|
|
458
563
|
}}
|
|
459
|
-
></umb-input-document
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
564
|
+
></umb-input-document>`,
|
|
565
|
+
)}
|
|
566
|
+
${property(
|
|
567
|
+
'And every page of these types',
|
|
568
|
+
'Wherever they are, unless a branch above is covered by another workflow',
|
|
569
|
+
html`<div class="checks">${this._list.contentTypes.map(
|
|
463
570
|
(t) =>
|
|
464
|
-
html`<label
|
|
571
|
+
html`<uui-checkbox label=${t.name} ?disabled=${!editable} ?checked=${w.appliesTo.contentTypes.includes(t.alias)} @change=${(
|
|
465
572
|
e,
|
|
466
573
|
) => {
|
|
467
574
|
w.appliesTo.contentTypes = e.target.checked
|
|
468
575
|
? [...w.appliesTo.contentTypes, t.alias]
|
|
469
576
|
: w.appliesTo.contentTypes.filter((a) => a !== t.alias)
|
|
470
577
|
this.#touch()
|
|
471
|
-
}}
|
|
472
|
-
)}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
SELF,
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
<label>When this workflow changes, pages waiting on it ${select(
|
|
501
|
-
w.onChange,
|
|
502
|
-
ON_CHANGE,
|
|
503
|
-
(v) => {
|
|
504
|
-
w.onChange = v
|
|
505
|
-
this.#touch()
|
|
506
|
-
},
|
|
507
|
-
)}</label>
|
|
508
|
-
</fieldset>
|
|
509
|
-
</uui-box>`
|
|
578
|
+
}}>${t.name}</uui-checkbox>`,
|
|
579
|
+
)}</div>`,
|
|
580
|
+
)}
|
|
581
|
+
</uui-box>
|
|
582
|
+
<uui-box headline="Rules">
|
|
583
|
+
${property(
|
|
584
|
+
'Publishers may skip it',
|
|
585
|
+
'People who may publish a page publish it directly',
|
|
586
|
+
html`<uui-toggle label="Publishers may skip it" ?disabled=${!editable} ?checked=${w.publishersMaySkip} @change=${(e) => set('publishersMaySkip')(e.target.checked)}></uui-toggle>`,
|
|
587
|
+
)}
|
|
588
|
+
${property(
|
|
589
|
+
'Unpublishing needs approval',
|
|
590
|
+
'Taking a page down goes through the workflow too',
|
|
591
|
+
html`<uui-toggle label="Unpublishing needs approval" ?disabled=${!editable} ?checked=${w.unpublish} @change=${(e) => set('unpublish')(e.target.checked)}></uui-toggle>`,
|
|
592
|
+
)}
|
|
593
|
+
${property('When a waiting page is changed', undefined, select('When a waiting page is changed', w.onEdit, ON_EDIT, set('onEdit'), !editable))}
|
|
594
|
+
${property(
|
|
595
|
+
'When the person who made the change could approve it',
|
|
596
|
+
undefined,
|
|
597
|
+
select('Self-approval', w.selfApproval, SELF, set('selfApproval'), !editable),
|
|
598
|
+
)}
|
|
599
|
+
${property('Comments on a decision', undefined, select('Comments', w.comments, COMMENTS, set('comments'), !editable))}
|
|
600
|
+
${property(
|
|
601
|
+
'When this workflow changes',
|
|
602
|
+
'What happens to pages already waiting on it',
|
|
603
|
+
select('When this workflow changes', w.onChange, ON_CHANGE, set('onChange'), !editable),
|
|
604
|
+
)}
|
|
605
|
+
</uui-box>
|
|
606
|
+
</div>`
|
|
510
607
|
}
|
|
511
608
|
|
|
512
609
|
static styles = css`
|
|
513
|
-
:host { display: block;
|
|
514
|
-
.
|
|
515
|
-
.
|
|
516
|
-
.
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
.
|
|
521
|
-
.
|
|
522
|
-
.
|
|
523
|
-
.
|
|
524
|
-
|
|
610
|
+
:host { display: block; height: 100%; container-type: inline-size; }
|
|
611
|
+
.list { padding: var(--uui-size-layout-1); }
|
|
612
|
+
.create { margin-bottom: var(--uui-size-space-5); }
|
|
613
|
+
.actions { text-align: right; white-space: nowrap; }
|
|
614
|
+
uui-tab-group { --uui-tab-divider: var(--uui-color-border); border-left: 1px solid var(--uui-color-border); border-right: 1px solid var(--uui-color-border); }
|
|
615
|
+
.notice { margin-bottom: var(--uui-size-layout-1); }
|
|
616
|
+
.toolbar { display: flex; flex-wrap: wrap; gap: var(--uui-size-space-3); }
|
|
617
|
+
.stages { display: grid; grid-template-columns: minmax(0, 1fr) minmax(280px, 380px); gap: var(--uui-size-layout-1); }
|
|
618
|
+
.stage { border-left: 1px solid var(--uui-color-border); padding-left: var(--uui-size-layout-1); }
|
|
619
|
+
.stage-header { display: flex; align-items: center; justify-content: space-between; gap: var(--uui-size-space-3); margin-bottom: var(--uui-size-space-3); }
|
|
620
|
+
.stage-header h4 { margin: 0; }
|
|
621
|
+
@container (max-width: 640px) {
|
|
622
|
+
.stages { grid-template-columns: minmax(0, 1fr); }
|
|
623
|
+
.stage { border-left: 0; padding-left: 0; border-top: 1px solid var(--uui-color-border); padding-top: var(--uui-size-layout-1); }
|
|
624
|
+
}
|
|
625
|
+
.settings { display: flex; flex-direction: column; gap: var(--uui-size-layout-1); }
|
|
626
|
+
.canvas { overflow: auto; border: 1px dashed var(--uui-color-border); border-radius: var(--uui-border-radius); padding: var(--uui-size-space-5); min-height: 240px; touch-action: none; }
|
|
627
|
+
umb-property-layout:first-of-type { padding-top: 0; }
|
|
628
|
+
uui-input, uui-select { width: 100%; }
|
|
629
|
+
uui-input.count { margin-top: var(--uui-size-space-3); max-width: 8em; }
|
|
630
|
+
.checks { display: flex; flex-direction: column; gap: var(--uui-size-space-2); }
|
|
631
|
+
.hint { margin-top: 0; color: var(--uui-color-text-alt); }
|
|
632
|
+
.message { margin: 0; color: var(--uui-color-warning-emphasis, #9e7300); }
|
|
633
|
+
.problems { color: var(--uui-color-danger); margin: 0; }
|
|
634
|
+
.not-saved { color: var(--uui-color-danger); }
|
|
635
|
+
.affected { border: 1px solid var(--uui-color-warning); }
|
|
525
636
|
${unsafeCSS(GRAPH_STYLES)}
|
|
526
637
|
`
|
|
527
638
|
}
|