@crombolo/backoffice-host 0.12.1 → 0.13.2

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.
@@ -14,6 +14,7 @@
14
14
  import '@umbraco-cms/backoffice/document'
15
15
  import { css, html, nothing, repeat } from '@umbraco-cms/backoffice/external/lit'
16
16
  import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'
17
+ import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'
17
18
  import {
18
19
  deleteForm,
19
20
  listForms,
@@ -61,8 +62,13 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
61
62
  _types: { state: true },
62
63
  }
63
64
 
65
+ #notifications
66
+
64
67
  constructor() {
65
68
  super()
69
+ this.consumeContext(UMB_NOTIFICATION_CONTEXT, (context) => {
70
+ this.#notifications = context
71
+ })
66
72
  this._forms = undefined
67
73
  this._editing = undefined
68
74
  this._problems = []
@@ -490,16 +496,19 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
490
496
  try {
491
497
  const result = await saveForm(this._editing)
492
498
  if (result.ok) {
493
- this._notice = 'Saved.'
499
+ // The green toast every workspace shows on a save.
500
+ this.#notifications?.peek('positive', { data: { message: 'Form saved' } })
494
501
  this._editing = undefined
495
502
  await this.#load()
496
503
  } else {
497
504
  this._problems = result.body.problems ?? []
498
- if (result.body.status === 'readOnly')
499
- this._notice = 'The schema directory is read-only on this node.'
505
+ if (result.body.status === 'readOnly' && this._problems.length === 0)
506
+ this._problems = [
507
+ { path: '', message: 'The schema directory is read-only on this node.' },
508
+ ]
500
509
  }
501
510
  } catch (error) {
502
- this._notice = error.message
511
+ this._problems = [{ path: '', message: error.message }]
503
512
  } finally {
504
513
  this._busy = false
505
514
  }
@@ -517,20 +526,19 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
517
526
  }
518
527
 
519
528
  render() {
520
- if (this._forms === undefined) return html`<uui-loader></uui-loader>`
521
- return html`
529
+ if (this._forms === undefined) return html`<div class="list"><uui-loader></uui-loader></div>`
530
+ if (this._editing) return this.#editor()
531
+ return html`<div class="list">
522
532
  ${this._notice ? html`<uui-box><p>${this._notice}</p></uui-box>` : nothing}
523
- ${this._editing ? this.#editor() : this.#list()}
524
- `
533
+ ${this.#list()}
534
+ </div>`
525
535
  }
526
536
 
527
537
  #list() {
538
+ // The create button above the list, as the workflows list has it.
528
539
  return html`
540
+ <uui-button class="create" color="default" look="outline" label="Create" @click=${() => this.#new()}></uui-button>
529
541
  <uui-box headline="Forms">
530
- <p>
531
- A form is a file in <code>schema/forms/</code>. Editing one here writes that
532
- file, so the change can be reviewed in a pull request like any other type.
533
- </p>
534
542
  ${
535
543
  this._forms.length === 0
536
544
  ? html`<p>There are no forms yet.</p>`
@@ -562,9 +570,6 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
562
570
  </uui-table>
563
571
  `
564
572
  }
565
- <div class="actions">
566
- <uui-button look="primary" label="Add a form" @click=${() => this.#new()}></uui-button>
567
- </div>
568
573
  </uui-box>
569
574
  `
570
575
  }
@@ -584,18 +589,14 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
584
589
  ></uui-input>
585
590
  </uui-form-layout-item>
586
591
  `
587
- return html`
588
- <uui-box headline=${form.alias ? `Form: ${form.name || form.alias}` : 'A new form'}>
589
- ${
590
- this._problems.length > 0
591
- ? html`<div class="problems" role="alert">
592
- <p>This form was not saved:</p>
593
- <ul>
594
- ${this._problems.map((problem) => html`<li><code>${problem.path}</code> ${problem.message}</li>`)}
595
- </ul>
596
- </div>`
597
- : nothing
598
- }
592
+ const leave = () => {
593
+ this._editing = undefined
594
+ this._problems = []
595
+ }
596
+ // Umbraco's own layout, as the workflow designer has it: Save bottom right,
597
+ // and what stopped a save listed just above it.
598
+ return html`<umb-body-layout headline=${form.alias ? `Form: ${form.name || form.alias}` : 'A new form'}>
599
+ <uui-box>
599
600
  ${text('name', 'Name')}
600
601
  ${text('alias', 'Alias', 'Letters and numbers, starting with a letter. The file is named after it.')}
601
602
  ${text('messageOnSubmit', 'Message on submit')}
@@ -633,14 +634,26 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
633
634
  ${this._editing.pages.map((page, p) => this.#page(page, p))}
634
635
  <div class="actions">
635
636
  <uui-button look="secondary" label="Add a page" @click=${() => this.#apply(addPage)}></uui-button>
636
- <uui-button look="primary" label="Save" ?disabled=${this._busy} @click=${() => this.#save()}></uui-button>
637
- <uui-button look="secondary" label="Cancel" @click=${() => {
638
- this._editing = undefined
639
- this._problems = []
640
- }}></uui-button>
641
637
  </div>
642
638
  </uui-box>
643
- `
639
+ ${
640
+ this._problems.length > 0
641
+ ? html`<div class="problems" role="alert">
642
+ <p>This form was not saved:</p>
643
+ <ul>
644
+ ${this._problems.map((problem) => html`<li>${problem.path ? html`<code>${problem.path}</code> ` : nothing}${problem.message}</li>`)}
645
+ </ul>
646
+ </div>`
647
+ : nothing
648
+ }
649
+ ${
650
+ this._problems.length > 0
651
+ ? html`<span slot="footer-info" class="not-saved">Not saved: ${this._problems.length} problem(s), listed above</span>`
652
+ : nothing
653
+ }
654
+ <uui-button slot="actions" look="secondary" label="Back to the list" @click=${leave}>Back to the list</uui-button>
655
+ <uui-button slot="actions" look="primary" color="positive" label="Save" ?disabled=${this._busy} @click=${() => this.#save()}>Save</uui-button>
656
+ </umb-body-layout>`
644
657
  }
645
658
 
646
659
  static styles = css`
@@ -701,8 +714,17 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
701
714
  }
702
715
  :host {
703
716
  display: block;
717
+ height: 100%;
718
+ }
719
+ .list {
704
720
  padding: var(--uui-size-layout-1, 24px);
705
721
  }
722
+ .create {
723
+ margin-bottom: var(--uui-size-space-5, 18px);
724
+ }
725
+ .not-saved {
726
+ color: var(--uui-color-danger, #d42054);
727
+ }
706
728
  p,
707
729
  ul {
708
730
  max-width: 70ch;
@@ -2,7 +2,7 @@
2
2
  "$schema": "../../../backoffice-dist/dist/umbraco-package-schema.json",
3
3
  "name": "Crombolo Localizations",
4
4
  "id": "Crombolo.Localizations",
5
- "version": "0.12.1",
5
+ "version": "0.13.2",
6
6
  "allowPublicAccess": false,
7
7
  "extensions": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "$schema": "../../../backoffice-dist/dist/umbraco-package-schema.json",
3
3
  "name": "Crombolo Search & sharing",
4
4
  "id": "Crombolo.Seo",
5
- "version": "0.12.1",
5
+ "version": "0.13.2",
6
6
  "allowPublicAccess": false,
7
7
  "extensions": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "$schema": "../../backoffice-dist/dist/umbraco-package-schema.json",
3
3
  "name": "Crombolo",
4
4
  "id": "Crombolo.Core",
5
- "version": "0.12.1",
5
+ "version": "0.13.2",
6
6
  "allowPublicAccess": false,
7
7
  "extensions": [
8
8
  {
@@ -246,7 +246,7 @@
246
246
  "alias": "Crombolo.SectionView.Forms.Overview",
247
247
  "name": "Crombolo Forms Overview",
248
248
  "element": "/crombolo-plugin/forms-overview.js",
249
- "weight": 200,
249
+ "weight": 100,
250
250
  "meta": {
251
251
  "label": "Forms",
252
252
  "pathname": "forms",
@@ -264,7 +264,7 @@
264
264
  "alias": "Crombolo.SectionView.Forms.Entries",
265
265
  "name": "Crombolo Forms Entries",
266
266
  "element": "/crombolo-plugin/forms-entries.js",
267
- "weight": 100,
267
+ "weight": 200,
268
268
  "meta": {
269
269
  "label": "Entries",
270
270
  "pathname": "entries",
@@ -345,6 +345,112 @@
345
345
  "name": "Crombolo Icons Context",
346
346
  "overwrites": ["Umb.GlobalContext.Icons"],
347
347
  "api": "/crombolo-plugin/branding/icon-registry.js"
348
+ },
349
+ {
350
+ "type": "section",
351
+ "alias": "Crombolo.Section.Workflow",
352
+ "name": "Crombolo Workflow Section",
353
+ "weight": 40,
354
+ "meta": {
355
+ "label": "Workflows",
356
+ "pathname": "workflow"
357
+ }
358
+ },
359
+ {
360
+ "type": "sectionView",
361
+ "alias": "Crombolo.SectionView.Workflow.Waiting",
362
+ "name": "Crombolo Workflow Waiting",
363
+ "element": "/crombolo-plugin/workflow-requests.js",
364
+ "elementName": "crombolo-workflow-waiting",
365
+ "weight": 400,
366
+ "meta": {
367
+ "label": "Waiting for me",
368
+ "pathname": "waiting",
369
+ "icon": "icon-inbox"
370
+ },
371
+ "conditions": [
372
+ {
373
+ "alias": "Umb.Condition.SectionAlias",
374
+ "match": "Crombolo.Section.Workflow"
375
+ }
376
+ ]
377
+ },
378
+ {
379
+ "type": "sectionView",
380
+ "alias": "Crombolo.SectionView.Workflow.InFlight",
381
+ "name": "Crombolo Workflow In Flight",
382
+ "element": "/crombolo-plugin/workflow-requests.js",
383
+ "elementName": "crombolo-workflow-in-flight",
384
+ "weight": 300,
385
+ "meta": {
386
+ "label": "In flight",
387
+ "pathname": "in-flight",
388
+ "icon": "icon-time"
389
+ },
390
+ "conditions": [
391
+ {
392
+ "alias": "Umb.Condition.SectionAlias",
393
+ "match": "Crombolo.Section.Workflow"
394
+ }
395
+ ]
396
+ },
397
+ {
398
+ "type": "sectionView",
399
+ "alias": "Crombolo.SectionView.Workflow.History",
400
+ "name": "Crombolo Workflow History",
401
+ "element": "/crombolo-plugin/workflow-requests.js",
402
+ "elementName": "crombolo-workflow-history",
403
+ "weight": 200,
404
+ "meta": {
405
+ "label": "Decided",
406
+ "pathname": "history",
407
+ "icon": "icon-check"
408
+ },
409
+ "conditions": [
410
+ {
411
+ "alias": "Umb.Condition.SectionAlias",
412
+ "match": "Crombolo.Section.Workflow"
413
+ }
414
+ ]
415
+ },
416
+ {
417
+ "type": "sectionView",
418
+ "alias": "Crombolo.SectionView.Workflow.Designer",
419
+ "name": "Crombolo Workflow Designer",
420
+ "element": "/crombolo-plugin/workflow-designer.js",
421
+ "weight": 100,
422
+ "meta": {
423
+ "label": "Workflows",
424
+ "pathname": "workflows",
425
+ "icon": "icon-mindmap"
426
+ },
427
+ "conditions": [
428
+ {
429
+ "alias": "Umb.Condition.SectionAlias",
430
+ "match": "Crombolo.Section.Workflow"
431
+ },
432
+ {
433
+ "alias": "Umb.Condition.CurrentUser.IsAdmin"
434
+ }
435
+ ]
436
+ },
437
+ {
438
+ "type": "workspaceView",
439
+ "alias": "Crombolo.WorkspaceView.Document.Workflow",
440
+ "name": "Crombolo Document Workflow",
441
+ "element": "/crombolo-plugin/workflow-tab.js",
442
+ "weight": 50,
443
+ "meta": {
444
+ "label": "Workflow",
445
+ "pathname": "workflow",
446
+ "icon": "icon-mindmap"
447
+ },
448
+ "conditions": [
449
+ {
450
+ "alias": "Umb.Condition.WorkspaceAlias",
451
+ "match": "Umb.Workspace.Document"
452
+ }
453
+ ]
348
454
  }
349
455
  ]
350
456
  }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The Workflow screens' calls to <backoffice>/crombolo/api/approvals/ (story 028).
3
+ * The cookie carries the session. Every answer resolves; a refusal carries the
4
+ * server's own words in `message`.
5
+ */
6
+ const apiBase = () =>
7
+ `${(document.querySelector('base')?.getAttribute('href') ?? '/umbraco/').replace(/\/$/, '')}/crombolo/api/approvals`
8
+
9
+ async function call(path, init = {}) {
10
+ const response = await fetch(`${apiBase()}/${path}`, {
11
+ credentials: 'include',
12
+ ...init,
13
+ headers: { accept: 'application/json', 'content-type': 'application/json', ...init.headers },
14
+ })
15
+ const body = await response.json().catch(() => ({}))
16
+ if (response.status === 403 && !body.message)
17
+ return { ok: false, message: 'You are not allowed to do that.' }
18
+ return { ok: response.ok, status: response.status, ...body }
19
+ }
20
+
21
+ const post = (path, body = {}) => call(path, { method: 'POST', body: JSON.stringify(body) })
22
+
23
+ export const pageApproval = (key) => call(`page/${encodeURIComponent(key)}`)
24
+ export const sendForApproval = (key, action, comment) =>
25
+ post(`page/${encodeURIComponent(key)}/send`, { action, comment })
26
+ export const decide = (requestKey, verb, body) =>
27
+ post(`request/${encodeURIComponent(requestKey)}/${verb}`, body)
28
+ export const waitingForMe = () => call('waiting')
29
+ export const inFlight = () => call('in-flight')
30
+ export const finished = (skip = 0, take = 25) => call(`history?skip=${skip}&take=${take}`)
31
+ export const listWorkflows = () => call('workflows')
32
+ export const previewWorkflow = (alias, workflow) =>
33
+ post(`workflows/${encodeURIComponent(alias)}/preview`, { workflow })
34
+ export const saveWorkflow = (alias, workflow) =>
35
+ call(`workflows/${encodeURIComponent(alias)}`, {
36
+ method: 'PUT',
37
+ body: JSON.stringify({ workflow }),
38
+ })
39
+ export const deleteWorkflow = (alias) =>
40
+ call(`workflows/${encodeURIComponent(alias)}`, { method: 'DELETE' })