diff --git a/examples/modal-routed/dashboard.element.ts b/examples/modal-routed/dashboard.element.ts new file mode 100644 index 0000000000..967a4a7a88 --- /dev/null +++ b/examples/modal-routed/dashboard.element.ts @@ -0,0 +1,47 @@ +import { css, html, LitElement, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import type { UmbRoute } from '@umbraco-cms/backoffice/router'; + +@customElement('umb-dashboard') +export class UmbDashboardElement extends UmbElementMixin(LitElement) { + @state() + private _routes: UmbRoute[] = [ + { + path: `/tab1`, + component: () => import('./tabs/tab1.element.js'), + }, + { + path: `/tab2`, + component: () => import('./tabs/tab2.element.js'), + }, + { + path: '', + redirectTo: 'tab1', + }, + ]; + + override render() { + return html` +
+ Dashboard 1 + +
+ +
+ `; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbDashboardElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-dashboard': UmbDashboardElement; + } +} diff --git a/examples/modal-routed/dashboard2.element.ts b/examples/modal-routed/dashboard2.element.ts new file mode 100644 index 0000000000..2a3f02e7bf --- /dev/null +++ b/examples/modal-routed/dashboard2.element.ts @@ -0,0 +1,34 @@ +import { css, html, LitElement, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; + +@customElement('umb-dashboard2') +export class UmbDashboard2Element extends UmbElementMixin(LitElement) { + constructor() { + super(); + } + + override render() { + return html` +
+

Link to modal route

+

+ This page only shows how to link to the routed modal that is placed on a tab on the "Modal Dashboard". + Clicking this link will not load the slots inside the modal, however, going to the "Modal Dashboard", clicking + on tab 2 and opening the modal from there will work. +

+ Open Modal Route +
+ `; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbDashboard2Element; + +declare global { + interface HTMLElementTagNameMap { + 'umb-dashboard2': UmbDashboard2Element; + } +} diff --git a/examples/modal-routed/index.ts b/examples/modal-routed/index.ts new file mode 100644 index 0000000000..5aca09fe79 --- /dev/null +++ b/examples/modal-routed/index.ts @@ -0,0 +1,44 @@ +import type { ManifestDashboard, ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; + +// const section : ManifestSection = { +// type: "section", +// alias: 'demo.section', +// name: "Demo Section", +// meta: { +// label: "Demo", +// pathname: "demo" +// } +// } + +const dashboard: ManifestDashboard = { + type: 'dashboard', + name: 'Example Modal Dashboard', + alias: 'example.dashboard.dataset', + element: () => import('./dashboard.element.js'), + weight: 15000, + meta: { + label: 'Modal Dashboard', + pathname: 'example', + }, +}; + +const dashboard2: ManifestDashboard = { + type: 'dashboard', + name: 'Example Modal Dashboard2', + alias: 'example.dashboard.dataset2', + element: () => import('./dashboard2.element.js'), + weight: 15001, + meta: { + label: 'Link Dashboard', + pathname: 'example-2', + }, +}; + +const modal: ManifestModal = { + type: 'modal', + name: 'Example Modal', + alias: 'example.routed.modal', + element: () => import('./modal/example-modal.element.js'), +}; + +export const manifests = [dashboard, dashboard2, modal]; diff --git a/examples/modal-routed/modal/example-modal-token.ts b/examples/modal-routed/modal/example-modal-token.ts new file mode 100644 index 0000000000..1755a7ef3a --- /dev/null +++ b/examples/modal-routed/modal/example-modal-token.ts @@ -0,0 +1,13 @@ +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export type Data = object; +export type ModalValue = object; + +export const EXAMPLE_ROUTED_MODAL = new UmbModalToken( + 'example.routed.modal', // this needs to match the alias of the modal registered in manifest.ts + { + modal: { + type: 'dialog', + }, + }, +); diff --git a/examples/modal-routed/modal/example-modal.element.ts b/examples/modal-routed/modal/example-modal.element.ts new file mode 100644 index 0000000000..e1f0f5bdd4 --- /dev/null +++ b/examples/modal-routed/modal/example-modal.element.ts @@ -0,0 +1,43 @@ +import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import type { UmbRoute } from '@umbraco-cms/backoffice/router'; + +@customElement('umb-example-modal') +export class UmbExampleModal extends UmbModalBaseElement { + @state() + private _routes: UmbRoute[] = [ + { + path: `modalOverview`, + component: () => import('./steps/example-modal-step1.element.js'), + }, + { + path: `details`, + component: () => import('./steps/example-modal-step2.element.js'), + }, + { + path: '', + redirectTo: 'modalOverview', + }, + ]; + + override render() { + return html` +
+ umb-example modal element +
+ +
+ `; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbExampleModal; + +declare global { + interface HTMLElementTagNameMap { + 'umb-example-modal': UmbExampleModal; + } +} diff --git a/examples/modal-routed/modal/steps/example-modal-step1.element.ts b/examples/modal-routed/modal/steps/example-modal-step1.element.ts new file mode 100644 index 0000000000..9098546e6a --- /dev/null +++ b/examples/modal-routed/modal/steps/example-modal-step1.element.ts @@ -0,0 +1,20 @@ +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; + +@customElement('umb-example-modal-step1') +export class UmbExampleModalStep1 extends UmbModalBaseElement { + override render() { + return html`
example modal step1
`; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbExampleModalStep1; + +declare global { + interface HTMLElementTagNameMap { + 'umb-example-modal-step1': UmbExampleModalStep1; + } +} diff --git a/examples/modal-routed/modal/steps/example-modal-step2.element.ts b/examples/modal-routed/modal/steps/example-modal-step2.element.ts new file mode 100644 index 0000000000..ea5c711b25 --- /dev/null +++ b/examples/modal-routed/modal/steps/example-modal-step2.element.ts @@ -0,0 +1,20 @@ +import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; + +@customElement('umb-example-modal-step2') +export class UmbExampleModalStep2 extends UmbModalBaseElement { + override render() { + return html`
example modal step2
`; + } + + static override styles = [UmbTextStyles]; +} + +export default UmbExampleModalStep2; + +declare global { + interface HTMLElementTagNameMap { + 'umb-example-modal-step2': UmbExampleModalStep2; + } +} diff --git a/examples/modal-routed/tabs/tab1.element.ts b/examples/modal-routed/tabs/tab1.element.ts new file mode 100644 index 0000000000..463b31824e --- /dev/null +++ b/examples/modal-routed/tabs/tab1.element.ts @@ -0,0 +1,31 @@ +import { css, html, LitElement, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; + +@customElement('umb-dashboard-tab1') +export class UmbDashboardTab1Element extends UmbElementMixin(LitElement) { + @state() + _editLinkPath?: string; + + constructor() { + super(); + } + + override render() { + return html` +
+

tab 1

+
+ `; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbDashboardTab1Element; + +declare global { + interface UmbDashboardTab1Element { + 'umb-dashboard-tab1': UmbDashboardTab1Element; + } +} diff --git a/examples/modal-routed/tabs/tab2.element.ts b/examples/modal-routed/tabs/tab2.element.ts new file mode 100644 index 0000000000..66346df3de --- /dev/null +++ b/examples/modal-routed/tabs/tab2.element.ts @@ -0,0 +1,55 @@ +import { EXAMPLE_ROUTED_MODAL } from '../modal/example-modal-token.js'; +import { css, html, LitElement, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; + +@customElement('umb-dashboard-tab2') +export class UmbDashboardTab2Element extends UmbElementMixin(LitElement) { + #workspaceModal?: UmbModalRouteRegistrationController< + typeof EXAMPLE_ROUTED_MODAL.DATA, + typeof EXAMPLE_ROUTED_MODAL.VALUE + >; + + @state() + _editLinkPath?: string; + + constructor() { + super(); + + // Using workspace modal context + this.#workspaceModal?.destroy(); + this.#workspaceModal = new UmbModalRouteRegistrationController(this, EXAMPLE_ROUTED_MODAL) + .addAdditionalPath('view/:entityKey') + .onSetup(() => { + return { + data: {}, + value: {}, + }; + }) + .observeRouteBuilder((routeBuilder) => { + this._editLinkPath = routeBuilder({ entityKey: 'abc123' }); + }); + } + + override render() { + return html` +
+

tab 2

+

This element hosts the UmbModalRouteRegistrationController

+ + Open modal +
+ `; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbDashboardTab2Element; + +declare global { + interface UmbDashboardTab2Element { + 'umb-dashboard-tab2': UmbDashboardTab2Element; + } +} diff --git a/examples/ufm-custom-component/index.ts b/examples/ufm-custom-component/index.ts index 67d3281921..69cda7ce89 100644 --- a/examples/ufm-custom-component/index.ts +++ b/examples/ufm-custom-component/index.ts @@ -7,8 +7,8 @@ export const manifests: Array = [ name: 'Custom UFM Component', api: () => import('./custom-ufm-component.js'), meta: { - marker: '%', alias: 'myCustomComponent', + marker: '%' }, }, ]; diff --git a/package-lock.json b/package-lock.json index 4ca5630445..ffc2c48c1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,21 +12,20 @@ "./src/packages/*" ], "dependencies": { - "@tiptap/core": "^2.8.0", - "@tiptap/extension-image": "^2.8.0", - "@tiptap/extension-link": "^2.8.0", - "@tiptap/extension-placeholder": "^2.8.0", - "@tiptap/extension-subscript": "^2.8.0", - "@tiptap/extension-superscript": "^2.8.0", - "@tiptap/extension-table": "^2.8.0", - "@tiptap/extension-table-cell": "^2.8.0", - "@tiptap/extension-table-header": "^2.8.0", - "@tiptap/extension-table-row": "^2.8.0", - "@tiptap/extension-text-align": "^2.8.0", - "@tiptap/extension-text-style": "^2.8.0", - "@tiptap/extension-underline": "^2.8.0", - "@tiptap/pm": "^2.8.0", - "@tiptap/starter-kit": "^2.8.0", + "@tiptap/core": "^2.9.1", + "@tiptap/extension-image": "^2.9.1", + "@tiptap/extension-link": "^2.9.1", + "@tiptap/extension-placeholder": "^2.9.1", + "@tiptap/extension-subscript": "^2.9.1", + "@tiptap/extension-superscript": "^2.9.1", + "@tiptap/extension-table": "^2.9.1", + "@tiptap/extension-table-cell": "^2.9.1", + "@tiptap/extension-table-header": "^2.9.1", + "@tiptap/extension-table-row": "^2.9.1", + "@tiptap/extension-text-align": "^2.9.1", + "@tiptap/extension-underline": "^2.9.1", + "@tiptap/pm": "^2.9.1", + "@tiptap/starter-kit": "^2.9.1", "@types/diff": "^5.2.1", "@types/dompurify": "^3.0.5", "@types/uuid": "^10.0.0", @@ -49,7 +48,7 @@ "@eslint/js": "^9.9.1", "@hey-api/openapi-ts": "^0.52.11", "@open-wc/testing": "^4.0.0", - "@playwright/test": "^1.45.3", + "@playwright/test": "^1.48.2", "@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", @@ -1332,12 +1331,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.46.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.1.tgz", - "integrity": "sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==", + "version": "1.48.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz", + "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==", "dev": true, "dependencies": { - "playwright": "1.46.1" + "playwright": "1.48.2" }, "bin": { "playwright": "cli.js" @@ -2318,9 +2317,9 @@ } }, "node_modules/@tiptap/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.8.0.tgz", - "integrity": "sha512-xsqDI4BNzYRWRtBq7+/38ThhqEr7uG9Njip1x+9/wgR3vWPBFnBkYJTz6jSxS35NRE6BSnERm4/B/vrLuY1Hdw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.9.1.tgz", + "integrity": "sha512-tifnLL/ARzQ6/FGEJjVwj9UT3v+pENdWHdk9x6F3X0mB1y0SeCjV21wpFLYESzwNdBPAj8NMp8Behv7dBnhIfw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2330,9 +2329,9 @@ } }, "node_modules/@tiptap/extension-blockquote": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.8.0.tgz", - "integrity": "sha512-m3CKrOIvV7fY1Ak2gYf5LkKiz6AHxHpg6wxfVaJvdBqXgLyVtHo552N+A4oSHOSRbB4AG9EBQ2NeBM8cdEQ4MA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.9.1.tgz", + "integrity": "sha512-Y0jZxc/pdkvcsftmEZFyG+73um8xrx6/DMfgUcNg3JAM63CISedNcr+OEI11L0oFk1KFT7/aQ9996GM6Kubdqg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2342,9 +2341,9 @@ } }, "node_modules/@tiptap/extension-bold": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.8.0.tgz", - "integrity": "sha512-U1YkZBxDkSLNvPNiqxB5g42IeJHr27C7zDb/yGQN2xL4UBeg4O9xVhCFfe32f6tLwivSL0dar4ScElpaCJuqow==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.9.1.tgz", + "integrity": "sha512-e2P1zGpnnt4+TyxTC5pX/lPxPasZcuHCYXY0iwQ3bf8qRQQEjDfj3X7EI+cXqILtnhOiviEOcYmeu5op2WhQDg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2354,23 +2353,21 @@ } }, "node_modules/@tiptap/extension-bullet-list": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.8.0.tgz", - "integrity": "sha512-H4O2X0ozbc/ce9/XF1H98sqWVUdtt7jzy7hMBunwmY8ZxI4dHtcRkeg81CZbpKTqOqRrMCLWjE3M2tgiDXrDkA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.9.1.tgz", + "integrity": "sha512-0hizL/0j9PragJObjAWUVSuGhN1jKjCFnhLQVRxtx4HutcvS/lhoWMvFg6ZF8xqWgIa06n6A7MaknQkqhTdhKA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/extension-list-item": "^2.7.0", - "@tiptap/extension-text-style": "^2.7.0" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-code": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.8.0.tgz", - "integrity": "sha512-VSFn3sFF6qPpOGkXFhik8oYRH5iByVJpFEFd/duIEftmS0MdPzkbSItOpN3mc9xsJ5dCX80LYaResSj5hr5zkA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.9.1.tgz", + "integrity": "sha512-WQqcVGe7i/E+yO3wz5XQteU1ETNZ00euUEl4ylVVmH2NM4Dh0KDjEhbhHlCM0iCfLUo7jhjC7dmS+hMdPUb+Tg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2380,9 +2377,9 @@ } }, "node_modules/@tiptap/extension-code-block": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.8.0.tgz", - "integrity": "sha512-POuA5Igx+Dto0DTazoBFAQTj/M/FCdkqRVD9Uhsxhv49swPyANTJRr05vgbgtHB+NDDsZfCawVh7pI0IAD/O0w==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.9.1.tgz", + "integrity": "sha512-A/50wPWDqEUUUPhrwRKILP5gXMO5UlQ0F6uBRGYB9CEVOREam9yIgvONOnZVJtszHqOayjIVMXbH/JMBeq11/g==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2393,9 +2390,9 @@ } }, "node_modules/@tiptap/extension-document": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.8.0.tgz", - "integrity": "sha512-mp7Isx1sVc/ifeW4uW/PexGQ9exN3NRUOebSpnLfqXeWYk4y1RS1PA/3+IHkOPVetbnapgPjFx/DswlCP3XLjA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.9.1.tgz", + "integrity": "sha512-1a+HCoDPnBttjqExfYLwfABq8MYdiowhy/wp8eCxVb6KGFEENO53KapstISvPzqH7eOi+qRjBB1KtVYb/ZXicg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2405,9 +2402,9 @@ } }, "node_modules/@tiptap/extension-dropcursor": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.8.0.tgz", - "integrity": "sha512-rAFvx44YuT6dtS1c+ALw0ROAGI16l5L1HxquL4hR1gtxDcTieST5xhw5bkshXlmrlfotZXPrhokzqA7qjhZtJw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.9.1.tgz", + "integrity": "sha512-wJZspSmJRkDBtPkzFz1g7gvZOEOayk8s93UHsgbJxcV4VWHYleZ5XhT74sZunSjefNDm3qC6v2BSgLp3vNHVKQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2418,9 +2415,9 @@ } }, "node_modules/@tiptap/extension-gapcursor": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.8.0.tgz", - "integrity": "sha512-Be1LWCmvteQInOnNVN+HTqc1XWsj1bCl+Q7et8qqNjtGtTaCbdCp8ppcH1SKJxNTM/RLUtPyJ8FDgOTj51ixCA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.9.1.tgz", + "integrity": "sha512-jsRBmX01vr+5H02GljiHMo0n5H1vzoMLmFarxe0Yq2d2l9G/WV2VWX2XnGliqZAYWd1bI0phs7uLQIN3mxGQTw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2431,9 +2428,9 @@ } }, "node_modules/@tiptap/extension-hard-break": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.8.0.tgz", - "integrity": "sha512-vqiIfviNiCmy/pJTHuDSCAGL2O4QDEdDmAvGJu8oRmElUrnlg8DbJUfKvn6DWQHNSQwRb+LDrwWlzAYj1K9u6A==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.9.1.tgz", + "integrity": "sha512-fCuaOD/b7nDjm47PZ58oanq7y4ccS2wjPh42Qm0B0yipu/1fmC8eS1SmaXmk28F89BLtuL6uOCtR1spe+lZtlQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2443,9 +2440,9 @@ } }, "node_modules/@tiptap/extension-heading": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.8.0.tgz", - "integrity": "sha512-4inWgrTPiqlivPmEHFOM5ck2UsmOsbKKPtqga6bALvWPmCv24S6/EBwFp8Jz4YABabXDnkviihmGu0LpP9D69w==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.9.1.tgz", + "integrity": "sha512-SjZowzLixOFaCrV2cMaWi1mp8REK0zK1b3OcVx7bCZfVSmsOETJyrAIUpCKA8o60NwF7pwhBg0MN8oXlNKMeFw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2455,9 +2452,9 @@ } }, "node_modules/@tiptap/extension-history": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.8.0.tgz", - "integrity": "sha512-u5YS0J5Egsxt8TUWMMAC3QhPZaak+IzQeyHch4gtqxftx96tprItY7AD/A3pGDF2uCSnN+SZrk6yVexm6EncDw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.9.1.tgz", + "integrity": "sha512-wp9qR1NM+LpvyLZFmdNaAkDq0d4jDJ7z7Fz7icFQPu31NVxfQYO3IXNmvJDCNu8hFAbImpA5aG8MBuwzRo0H9w==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2468,9 +2465,9 @@ } }, "node_modules/@tiptap/extension-horizontal-rule": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.8.0.tgz", - "integrity": "sha512-Sn/MI8WVFBoIYSIHA9NJryJIyCEzZdRysau8pC5TFnfifre0QV1ksPz2bgF+DyCD69ozQiRdBBHDEwKe47ZbfQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.9.1.tgz", + "integrity": "sha512-ydUhABeaBI1CoJp+/BBqPhXINfesp1qMNL/jiDcMsB66fsD4nOyphpAJT7FaRFZFtQVF06+nttBtFZVkITQVqg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2481,9 +2478,9 @@ } }, "node_modules/@tiptap/extension-image": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.8.0.tgz", - "integrity": "sha512-5CReomgHGTUgxaX8P3i6qiC9VRWcWQgVoYtds4ZM52LVx/oGwMxQ4ECyzdVYKaRW+6PrNnAe6ew3Qpd5Wk0cIg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.9.1.tgz", + "integrity": "sha512-aGqJnsuS8oagIhsx7wetm8jw4NEDsOV0OSx4FQ4VPlUqWlnzK0N+erFKKJmXTdAxL8PGzoPSlITFH63MV3eV3Q==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2493,9 +2490,9 @@ } }, "node_modules/@tiptap/extension-italic": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.8.0.tgz", - "integrity": "sha512-PwwSE2LTYiHI47NJnsfhBmPiLE8IXZYqaSoNPU6flPrk1KxEzqvRI1joKZBmD9wuqzmHJ93VFIeZcC+kfwi8ZA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.9.1.tgz", + "integrity": "sha512-VkNA6Vz96+/+7uBlsgM7bDXXx4b62T1fDam/3UKifA72aD/fZckeWrbT7KrtdUbzuIniJSbA0lpTs5FY29+86Q==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2505,9 +2502,9 @@ } }, "node_modules/@tiptap/extension-link": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.8.0.tgz", - "integrity": "sha512-p67hCG/pYCiOK/oCTPZnlkw9Ei7KJ7kCKFaluTcAmr5j8IBdYfDqSMDNCT4vGXBvKFh4X6xD7S7QvOqcH0Gn9A==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.9.1.tgz", + "integrity": "sha512-yG+e3e8cCCN9dZjX4ttEe3e2xhh58ryi3REJV4MdiEkOT9QF75Bl5pUbMIS4tQ8HkOr04QBFMHKM12kbSxg1BA==", "dependencies": { "linkifyjs": "^4.1.0" }, @@ -2521,9 +2518,9 @@ } }, "node_modules/@tiptap/extension-list-item": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.8.0.tgz", - "integrity": "sha512-o7OGymGxB0B9x3x2prp3KBDYFuBYGc5sW69O672jk8G52DqhzzndgPnkk0qUn8nXAUKuDGbJmpmHVA2kagqnRg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.9.1.tgz", + "integrity": "sha512-6O4NtYNR5N2Txi4AC0/4xMRJq9xd4+7ShxCZCDVL0WDVX37IhaqMO7LGQtA6MVlYyNaX4W1swfdJaqrJJ5HIUw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2533,23 +2530,21 @@ } }, "node_modules/@tiptap/extension-ordered-list": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.8.0.tgz", - "integrity": "sha512-sCvNbcTS1+5QTTXwUPFa10vf5I1pr8sGcOTIh0G+a5ZkS5+6FxT12k7VLzPt39QyNbOi+77U2o4Xr4XyaEkfSg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.9.1.tgz", + "integrity": "sha512-6J9jtv1XP8dW7/JNSH/K4yiOABc92tBJtgCsgP8Ep4+fjfjdj4HbjS1oSPWpgItucF2Fp/VF8qg55HXhjxHjTw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/extension-list-item": "^2.7.0", - "@tiptap/extension-text-style": "^2.7.0" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-paragraph": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.8.0.tgz", - "integrity": "sha512-XgxxNNbuBF48rAGwv7/s6as92/xjm/lTZIGTq9aG13ClUKFtgdel7C33SpUCcxg3cO2WkEyllXVyKUiauFZw/A==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.9.1.tgz", + "integrity": "sha512-JOmT0xd4gd3lIhLwrsjw8lV+ZFROKZdIxLi0Ia05XSu4RLrrvWj0zdKMSB+V87xOWfSB3Epo95zAvnPox5Q16A==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2559,9 +2554,9 @@ } }, "node_modules/@tiptap/extension-placeholder": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.8.0.tgz", - "integrity": "sha512-BMqv/C9Tcjd7L1/OphUAJTZhWfpWs0rTQJ0bs3RRGsC8L+K20Fg+li45vw7M0teojpfrw57zwJogJd/m23Zr1Q==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.9.1.tgz", + "integrity": "sha512-Q/w3OOg/C6jGBf4QKEWKF9k+iaCQCgPoaIg2IDTPx8QmaxRfgoVE5Csd+oTOY/brdmSNXOxykZWEci6OJP+MbA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2572,9 +2567,9 @@ } }, "node_modules/@tiptap/extension-strike": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.8.0.tgz", - "integrity": "sha512-ezkDiXxQ3ME/dDMMM7tAMkKRi6UWw7tIu+Mx7Os0z8HCGpVBk1gFhLlhEd8I5rJaPZr4tK1wtSehMA9bscFGQw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.9.1.tgz", + "integrity": "sha512-V5aEXdML+YojlPhastcu7w4biDPwmzy/fWq0T2qjfu5Te/THcqDmGYVBKESBm5x6nBy5OLkanw2O+KHu2quDdg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2584,9 +2579,9 @@ } }, "node_modules/@tiptap/extension-subscript": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-2.8.0.tgz", - "integrity": "sha512-m14K5M7E+SqqrBul+B9t5sjN4zqTddV+Q+vd+RIm+OHG6AQhwewNoFyghZz5dGZ2Xj7HqiEyusBN+iHwfgJpmg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-2.9.1.tgz", + "integrity": "sha512-jjfuHmF2dCUAtHmJH2K/7HhOCleM3aPVOI/UsBBYa8xM4mDU4xuW1O5sLAr2JWcB1xxyk9YKcBWwyRq+b1ENFA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2596,9 +2591,9 @@ } }, "node_modules/@tiptap/extension-superscript": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-2.8.0.tgz", - "integrity": "sha512-3rAVyRvzhoM51vaeIAEXmr2PkucIwv7ptgyxg6zx6STxcyzMchafGee0LJL7Kcn9uE/n7Yt7ek6bDqo8jU8CtA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-2.9.1.tgz", + "integrity": "sha512-7cgAPpUNgO/3QdvCN9/6dWP6JQC641o8dSgkyv0XzVv0nxISck4SU+2eADRYQLyP2s4M3xuSEFhCCiKZleK2yA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2608,9 +2603,9 @@ } }, "node_modules/@tiptap/extension-table": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.8.0.tgz", - "integrity": "sha512-dm9CitjacXyJuE5SZfV2lUc3uOiP2sxo6fygIzMz7iuxHqQueyONWG+TBkK7HjqzXOiMPsvOf/25NazzIG8HMg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.9.1.tgz", + "integrity": "sha512-OmWZFZOSZwSSEvoVUkDsRFyCXTYei/pV396Xjv9pfFzXQkVbfq/CjTp61zvb/9mmEz3rcfvfG7G39eRlZTvBNg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2621,9 +2616,9 @@ } }, "node_modules/@tiptap/extension-table-cell": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.8.0.tgz", - "integrity": "sha512-IZpxONWyOd474L8+k4bHrFNRhbsl9eRwbNs5O877JkVFItc2WUz1DIhbJzjmBRsqExtWQJuOsiqWFab1kpiwGQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.9.1.tgz", + "integrity": "sha512-/wrcniLdhMhs5M2NDetFcfq510N5to7YKK+52KOXNotBI8K/GjMmGmtwWEKPITD0/RgYrXzpMcta/O+/0OCOPQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2633,9 +2628,9 @@ } }, "node_modules/@tiptap/extension-table-header": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.8.0.tgz", - "integrity": "sha512-B67A96yMQlG96IFzZBc7D5dnn7O29hcjuDLtjyZkKvU5D/RlFKPMmC9nVphCV3CnbkvEOZUdK9pNaOpen64naw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.9.1.tgz", + "integrity": "sha512-KtI01636Du1IB/I3pe9ZJWKkOc6INqAaIw+RFirRCnd8Xnik7tJfAwdhXzoPRcer6ViZmlzSrM2dkwaZCF7gcw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2645,9 +2640,9 @@ } }, "node_modules/@tiptap/extension-table-row": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.8.0.tgz", - "integrity": "sha512-Iezej6l7X+WqKzGLmCgAwmpL+QsfjFv1g8yVH5d0/3Pkcj3G9nDn+GSm4bZnbfYFyqInHG94PZ5PMReiALrJtA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.9.1.tgz", + "integrity": "sha512-Wq7QlI/S5iX4UCAdX+ok/szegVMbvrM3H8o6jwO+G4p8JJt6iv7ZmEnJ19xIINhmiKsrdanqH9FFK4tQ3yvQ0A==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2657,9 +2652,9 @@ } }, "node_modules/@tiptap/extension-text": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.8.0.tgz", - "integrity": "sha512-EDAdFFzWOvQfVy7j3qkKhBpOeE5thkJaBemSWfXI93/gMVc0ZCdLi24mDvNNgUHlT+RjlIoQq908jZaaxLKN2A==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.9.1.tgz", + "integrity": "sha512-3wo9uCrkLVLQFgbw2eFU37QAa1jq1/7oExa+FF/DVxdtHRS9E2rnUZ8s2hat/IWzvPUHXMwo3Zg2XfhoamQpCA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2669,9 +2664,9 @@ } }, "node_modules/@tiptap/extension-text-align": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.8.0.tgz", - "integrity": "sha512-Y6s/DF+P4lxpAnvSrnmt4xGwQT/AJJJm0aA1wu5GuPKpAQ+K4C7K6rE6uGNAXtR39GlewC7KdmcvA+CYhL8xlw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.9.1.tgz", + "integrity": "sha512-oUp0XnwJpAImcOVV68vsY2CpkHpRZ3gzWfIRTuy+aYitQim3xDKis/qfWQUWZsANp9/TZ0VyjtkZxNMwOfcu1g==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2681,9 +2676,9 @@ } }, "node_modules/@tiptap/extension-text-style": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.8.0.tgz", - "integrity": "sha512-jJp0vcZ2Ty7RvIL0VU6dm1y+fTfXq1lN2GwtYzYM0ueFuESa+Qo8ticYOImyWZ3wGJGVrjn7OV9r0ReW0/NYkQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.9.1.tgz", + "integrity": "sha512-LAxc0SeeiPiAVBwksczeA7BJSZb6WtVpYhy5Esvy9K0mK5kttB4KxtnXWeQzMIJZQbza65yftGKfQlexf/Y7yg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2693,9 +2688,9 @@ } }, "node_modules/@tiptap/extension-underline": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.8.0.tgz", - "integrity": "sha512-1ouuHwZJphT8OosAmp6x8e+Wly3cUd1pNWBiOutJX+6QRGBXJnIKFCzn8YOTlWhg1YQigisG7dNF3YdlyuRNHw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.9.1.tgz", + "integrity": "sha512-IrUsIqKPgD7GcAjr4D+RC0WvLHUDBTMkD8uPNEoeD1uH9t9zFyDfMRPnx/z3/6Gf6fTh3HzLcHGibiW2HiMi2A==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2705,9 +2700,9 @@ } }, "node_modules/@tiptap/pm": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.8.0.tgz", - "integrity": "sha512-eMGpRooUMvKz/vOpnKKppApMSoNM325HxTdAJvTlVAmuHp5bOY5kyY1kfUlePRiVx1t1UlFcXs3kecFwkkBD3Q==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.9.1.tgz", + "integrity": "sha512-mvV86fr7kEuDYEApQ2uMPCKL2uagUE0BsXiyyz3KOkY1zifyVm1fzdkscb24Qy1GmLzWAIIihA+3UHNRgYdOlQ==", "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", @@ -2726,7 +2721,7 @@ "prosemirror-tables": "^1.4.0", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.0", - "prosemirror-view": "^1.33.10" + "prosemirror-view": "^1.34.3" }, "funding": { "type": "github", @@ -2734,30 +2729,31 @@ } }, "node_modules/@tiptap/starter-kit": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.8.0.tgz", - "integrity": "sha512-r7UwaTrECkQoheWVZKFDqtL5tBx07x7IFT+prfgnsVlYFutGWskVVqzCDvD3BDmrg5PzeCWYZrQGlPaLib7tjg==", - "dependencies": { - "@tiptap/core": "^2.8.0", - "@tiptap/extension-blockquote": "^2.8.0", - "@tiptap/extension-bold": "^2.8.0", - "@tiptap/extension-bullet-list": "^2.8.0", - "@tiptap/extension-code": "^2.8.0", - "@tiptap/extension-code-block": "^2.8.0", - "@tiptap/extension-document": "^2.8.0", - "@tiptap/extension-dropcursor": "^2.8.0", - "@tiptap/extension-gapcursor": "^2.8.0", - "@tiptap/extension-hard-break": "^2.8.0", - "@tiptap/extension-heading": "^2.8.0", - "@tiptap/extension-history": "^2.8.0", - "@tiptap/extension-horizontal-rule": "^2.8.0", - "@tiptap/extension-italic": "^2.8.0", - "@tiptap/extension-list-item": "^2.8.0", - "@tiptap/extension-ordered-list": "^2.8.0", - "@tiptap/extension-paragraph": "^2.8.0", - "@tiptap/extension-strike": "^2.8.0", - "@tiptap/extension-text": "^2.8.0", - "@tiptap/pm": "^2.8.0" + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.9.1.tgz", + "integrity": "sha512-nsw6UF/7wDpPfHRhtGOwkj1ipIEiWZS1VGw+c14K61vM1CNj0uQ4jogbHwHZqN1dlL5Hh+FCqUHDPxG6ECbijg==", + "dependencies": { + "@tiptap/core": "^2.9.1", + "@tiptap/extension-blockquote": "^2.9.1", + "@tiptap/extension-bold": "^2.9.1", + "@tiptap/extension-bullet-list": "^2.9.1", + "@tiptap/extension-code": "^2.9.1", + "@tiptap/extension-code-block": "^2.9.1", + "@tiptap/extension-document": "^2.9.1", + "@tiptap/extension-dropcursor": "^2.9.1", + "@tiptap/extension-gapcursor": "^2.9.1", + "@tiptap/extension-hard-break": "^2.9.1", + "@tiptap/extension-heading": "^2.9.1", + "@tiptap/extension-history": "^2.9.1", + "@tiptap/extension-horizontal-rule": "^2.9.1", + "@tiptap/extension-italic": "^2.9.1", + "@tiptap/extension-list-item": "^2.9.1", + "@tiptap/extension-ordered-list": "^2.9.1", + "@tiptap/extension-paragraph": "^2.9.1", + "@tiptap/extension-strike": "^2.9.1", + "@tiptap/extension-text": "^2.9.1", + "@tiptap/extension-text-style": "^2.9.1", + "@tiptap/pm": "^2.9.1" }, "funding": { "type": "github", @@ -13332,12 +13328,12 @@ } }, "node_modules/playwright": { - "version": "1.46.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz", - "integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==", + "version": "1.48.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz", + "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==", "dev": true, "dependencies": { - "playwright-core": "1.46.1" + "playwright-core": "1.48.2" }, "bin": { "playwright": "cli.js" @@ -13350,9 +13346,9 @@ } }, "node_modules/playwright-core": { - "version": "1.46.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz", - "integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==", + "version": "1.48.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz", + "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -13814,9 +13810,9 @@ } }, "node_modules/prosemirror-view": { - "version": "1.34.2", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.34.2.tgz", - "integrity": "sha512-tPX/V2Xd70vrAGQ/V9CppJtPKnQyQMypJGlLylvdI94k6JaG+4P6fVmXPR1zc1eVTW0gq3c6zsfqwJKCRLaG9Q==", + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.35.0.tgz", + "integrity": "sha512-Umtbh22fmUlpZpRTiOVXA0PpdRZeYEeXQsLp51VfnMhjkJrqJ0n8APinIZrRAD5Jr3UxH8FnOaUqRylSuMsqHA==", "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", diff --git a/package.json b/package.json index 168c6ae73d..1c7df1a50b 100644 --- a/package.json +++ b/package.json @@ -198,21 +198,20 @@ "npm": ">=10.1 < 11" }, "dependencies": { - "@tiptap/core": "^2.8.0", - "@tiptap/extension-image": "^2.8.0", - "@tiptap/extension-link": "^2.8.0", - "@tiptap/extension-placeholder": "^2.8.0", - "@tiptap/extension-subscript": "^2.8.0", - "@tiptap/extension-superscript": "^2.8.0", - "@tiptap/extension-table": "^2.8.0", - "@tiptap/extension-table-cell": "^2.8.0", - "@tiptap/extension-table-header": "^2.8.0", - "@tiptap/extension-table-row": "^2.8.0", - "@tiptap/extension-text-align": "^2.8.0", - "@tiptap/extension-text-style": "^2.8.0", - "@tiptap/extension-underline": "^2.8.0", - "@tiptap/pm": "^2.8.0", - "@tiptap/starter-kit": "^2.8.0", + "@tiptap/core": "^2.9.1", + "@tiptap/extension-image": "^2.9.1", + "@tiptap/extension-link": "^2.9.1", + "@tiptap/extension-placeholder": "^2.9.1", + "@tiptap/extension-subscript": "^2.9.1", + "@tiptap/extension-superscript": "^2.9.1", + "@tiptap/extension-table": "^2.9.1", + "@tiptap/extension-table-cell": "^2.9.1", + "@tiptap/extension-table-header": "^2.9.1", + "@tiptap/extension-table-row": "^2.9.1", + "@tiptap/extension-text-align": "^2.9.1", + "@tiptap/extension-underline": "^2.9.1", + "@tiptap/pm": "^2.9.1", + "@tiptap/starter-kit": "^2.9.1", "@types/diff": "^5.2.1", "@types/dompurify": "^3.0.5", "@types/uuid": "^10.0.0", @@ -235,7 +234,7 @@ "@eslint/js": "^9.9.1", "@hey-api/openapi-ts": "^0.52.11", "@open-wc/testing": "^4.0.0", - "@playwright/test": "^1.45.3", + "@playwright/test": "^1.48.2", "@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", diff --git a/src/assets/lang/de-ch.ts b/src/assets/lang/de-ch.ts new file mode 100644 index 0000000000..cd33f22eab --- /dev/null +++ b/src/assets/lang/de-ch.ts @@ -0,0 +1,17 @@ +/** + * Creator Name: The Umbraco community + * Creator Link: https://docs.umbraco.com/umbraco-cms/extending/language-files + * + * Language Alias: de_CH + * Language Int Name: German Switzerland (DE-CH) + * Language Local Name: Deutsch Schweiz (DE-CH) + * Language LCID: 7 + * Language Culture: de-CH + */ +import de_de from './de-de.js'; +import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localization-api'; + +export default { + // NOTE: Imports and re-exports the German (Germany) localizations, so that any German (Switzerland) localizations can be override them. [LK] + ...de_de, +} as UmbLocalizationDictionary; diff --git a/src/assets/lang/en.ts b/src/assets/lang/en.ts index 27fb01708b..e5a5eeadc2 100644 --- a/src/assets/lang/en.ts +++ b/src/assets/lang/en.ts @@ -1978,7 +1978,7 @@ export default { passwordCurrent: 'Current password', passwordInvalid: 'Invalid current password', passwordIsDifferent: - 'There was a difference between the new password and the confirmed password. Please\n try again!\n ', + 'There was a difference between the new password and the confirmed password. Please try again!', passwordMismatch: "The confirmed password doesn't match the new password!", passwordRequiresDigit: "The password must have at least one digit ('0'-'9')", passwordRequiresLower: "The password must have at least one lowercase ('a'-'z')", diff --git a/src/assets/lang/fr-ch.ts b/src/assets/lang/fr-ch.ts new file mode 100644 index 0000000000..712e9fc0fd --- /dev/null +++ b/src/assets/lang/fr-ch.ts @@ -0,0 +1,17 @@ +/** + * Creator Name: The Umbraco community + * Creator Link: https://docs.umbraco.com/umbraco-cms/extending/language-files + * + * Language Alias: fr_ch + * Language Int Name: French Switzerland (FR-CH) + * Language Local Name: Français Suisse (FR-CH) + * Language LCID: 12 + * Language Culture: fr-CH + */ +import fr_fr from './fr-fr.js'; +import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localization-api'; + +export default { + // NOTE: Imports and re-exports the French (France) localizations, so that any French (Switzerland) localizations can be override them. [LK] + ...fr_fr, +} as UmbLocalizationDictionary; diff --git a/src/assets/lang/it-ch.ts b/src/assets/lang/it-ch.ts new file mode 100644 index 0000000000..bdba6cf8c1 --- /dev/null +++ b/src/assets/lang/it-ch.ts @@ -0,0 +1,17 @@ +/** + * Creator Name: The Umbraco community + * Creator Link: https://docs.umbraco.com/umbraco-cms/extending/language-files + * + * Language Alias: it_ch + * Language Int Name: Italian Switzerland (IT-CH) + * Language Local Name: Italiano Svizerra (IT-CH) + * Language LCID: 16 + * Language Culture: it-CH + */ +import it_it from './it-it.js'; +import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localization-api'; + +export default { + // NOTE: Imports and re-exports the Italian (Italy) localizations, so that any Italian (Switzerland) localizations can be override them. [LK] + ...it_it, +} as UmbLocalizationDictionary; diff --git a/src/external/router-slot/router-slot.ts b/src/external/router-slot/router-slot.ts index 06fddd335b..b93152ba63 100644 --- a/src/external/router-slot/router-slot.ts +++ b/src/external/router-slot/router-slot.ts @@ -162,9 +162,11 @@ export class RouterSlot extends HTMLElement implements IRouter this._setParent(null); } } - if (this.parent && this.parent.match !== null && this.match === null) { + if (this.parent) { requestAnimationFrame(() => { - this.render(); + if (this.parent && this.parent.match !== null && this.match === null) { + this.render(); + } }); } } diff --git a/src/libs/context-api/consume/context-consumer.controller.ts b/src/libs/context-api/consume/context-consumer.controller.ts index 892264047d..573cb5aadd 100644 --- a/src/libs/context-api/consume/context-consumer.controller.ts +++ b/src/libs/context-api/consume/context-consumer.controller.ts @@ -19,7 +19,7 @@ export class UmbContextConsumerController, callback?: UmbContextCallback, ) { - super(host.getHostElement(), contextAlias, callback); + super(() => host.getHostElement(), contextAlias, callback); this.#host = host; host.addUmbController(this); } diff --git a/src/libs/context-api/consume/context-consumer.test.ts b/src/libs/context-api/consume/context-consumer.test.ts index e45ea4170a..5e110b74ae 100644 --- a/src/libs/context-api/consume/context-consumer.test.ts +++ b/src/libs/context-api/consume/context-consumer.test.ts @@ -2,12 +2,12 @@ import { UmbContextProvider } from '../provide/context-provider.js'; import { UmbContextToken } from '../token/context-token.js'; import { UmbContextConsumer } from './context-consumer.js'; import type { UmbContextRequestEventImplementation } from './context-request.event.js'; -import { UMB_CONTENT_REQUEST_EVENT_TYPE } from './context-request.event.js'; +import { UMB_CONTEXT_REQUEST_EVENT_TYPE } from './context-request.event.js'; import { expect, oneEvent } from '@open-wc/testing'; const testContextAlias = 'my-test-context'; const testContextAliasAndApiAlias = 'my-test-context#testApi'; -const testContextAliasAndNotExstingApiAlias = 'my-test-context#notExistingTestApi'; +const testContextAliasAndNotExistingApiAlias = 'my-test-context#notExistingTestApi'; class UmbTestContextConsumerClass { public prop: string = 'value from provider'; @@ -38,13 +38,13 @@ describe('UmbContextConsumer', () => { describe('events', () => { it('dispatches context request event when constructed', async () => { - const listener = oneEvent(window, UMB_CONTENT_REQUEST_EVENT_TYPE); + const listener = oneEvent(window, UMB_CONTEXT_REQUEST_EVENT_TYPE); consumer.hostConnected(); const event = (await listener) as unknown as UmbContextRequestEventImplementation; expect(event).to.exist; - expect(event.type).to.eq(UMB_CONTENT_REQUEST_EVENT_TYPE); + expect(event.type).to.eq(UMB_CONTEXT_REQUEST_EVENT_TYPE); expect(event.contextAlias).to.eq(testContextAlias); consumer.hostDisconnected(); }); @@ -74,6 +74,47 @@ describe('UmbContextConsumer', () => { localConsumer.hostConnected(); }); + it('works with host as a method', (done) => { + const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass()); + provider.hostConnected(); + + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer( + () => element, + testContextAlias, + (_instance: UmbTestContextConsumerClass | undefined) => { + if (_instance) { + expect(_instance.prop).to.eq('value from provider'); + done(); + localConsumer.hostDisconnected(); + provider.hostDisconnected(); + } + }, + ); + localConsumer.hostConnected(); + }); + + it('works with host method returning undefined', async () => { + const element = undefined; + + const localConsumer = new UmbContextConsumer( + () => element, + testContextAlias, + (_instance: UmbTestContextConsumerClass | undefined) => { + if (_instance) { + expect.fail('Callback should not be called when never permitted'); + } + }, + ); + localConsumer.hostConnected(); + + await Promise.resolve(); + + localConsumer.hostDisconnected(); + }); + /* Unprovided feature is out commented currently. I'm not sure there is a use case. So lets leave the code around until we know for sure. it('acts to Context API disconnected', (done) => { @@ -139,7 +180,7 @@ describe('UmbContextConsumer', () => { const element = document.createElement('div'); document.body.appendChild(element); - const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExstingApiAlias, () => { + const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExistingApiAlias, () => { expect(false).to.be.true; }); localConsumer.hostConnected(); diff --git a/src/libs/context-api/consume/context-consumer.ts b/src/libs/context-api/consume/context-consumer.ts index f4eab04f67..0230a3f1fd 100644 --- a/src/libs/context-api/consume/context-consumer.ts +++ b/src/libs/context-api/consume/context-consumer.ts @@ -3,11 +3,13 @@ import { isUmbContextProvideEventType, UMB_CONTEXT_PROVIDE_EVENT_TYPE } from '.. import type { UmbContextCallback } from './context-request.event.js'; import { UmbContextRequestEventImplementation } from './context-request.event.js'; +type HostElementMethod = () => Element | undefined; + /** * @class UmbContextConsumer */ export class UmbContextConsumer { - protected _host: Element; + protected _retrieveHost: HostElementMethod; #skipHost?: boolean; #stopAtContextMatch = true; @@ -33,11 +35,15 @@ export class UmbContextConsumer, callback?: UmbContextCallback, ) { - this._host = host; + if (typeof host === 'function') { + this._retrieveHost = host; + } else { + this._retrieveHost = () => host; + } const idSplit = contextIdentifier.toString().split('#'); this.#contextAlias = idSplit[0]; this.#apiAlias = idSplit[1] ?? 'default'; @@ -72,6 +78,10 @@ export class UmbContextConsumer = (instance: T) => void; @@ -29,7 +34,7 @@ export class UmbContextRequestEventImplementation public readonly callback: (context: ResultType) => boolean, public readonly stopAtContextMatch: boolean = true, ) { - super(UMB_CONTENT_REQUEST_EVENT_TYPE, { bubbles: true, composed: true, cancelable: true }); + super(UMB_CONTEXT_REQUEST_EVENT_TYPE, { bubbles: true, composed: true, cancelable: true }); } clone() { diff --git a/src/libs/context-api/provide/context-boundary.controller.ts b/src/libs/context-api/provide/context-boundary.controller.ts new file mode 100644 index 0000000000..6fa5b7d1b1 --- /dev/null +++ b/src/libs/context-api/provide/context-boundary.controller.ts @@ -0,0 +1,40 @@ +import type { UmbContextToken } from '../token/index.js'; +import { UmbContextBoundary } from './context-boundary.js'; +import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbContextBoundaryController extends UmbContextBoundary implements UmbController { + #host: UmbControllerHost; + #controllerAlias: string; + + public get controllerAlias(): string { + return this.#controllerAlias; + } + + constructor(host: UmbControllerHost, contextAlias: string | UmbContextToken) { + super(host.getHostElement(), contextAlias); + this.#host = host; + // Makes the controllerAlias unique for this instance, this enables multiple Contexts to be provided under the same name. (This only makes sense cause of Context Token Discriminators) + // This does mean that if someone provides a context with the same name, but with a different instance, it will not override the previous instance. But its good since it enables extensions to provide contexts at the same scope of other contexts. + this.#controllerAlias = 'umbContextBoundary_' + contextAlias.toString(); + + // If this API is already provided with this alias? Then we do not want to register this controller: + const existingControllers = host.getUmbControllers((x) => x.controllerAlias === this.controllerAlias); + if (existingControllers.length > 0) { + // This just an additional awareness feature to make devs Aware, the alternative would be adding it anyway, but that would destroy existing controller of this alias. + // Back out, this instance is already provided, by another controller. + throw new Error( + `Context API: The context boundary of '${this.controllerAlias}' is already provided by another Context Provider Controller.`, + ); + } else { + host.addUmbController(this); + } + } + + public override destroy(): void { + if (this.#host) { + this.#host.removeUmbController(this); + (this.#host as unknown) = undefined; + } + super.destroy(); + } +} diff --git a/src/libs/context-api/provide/context-boundary.ts b/src/libs/context-api/provide/context-boundary.ts new file mode 100644 index 0000000000..56244997c6 --- /dev/null +++ b/src/libs/context-api/provide/context-boundary.ts @@ -0,0 +1,60 @@ +import type { UmbContextRequestEvent } from '../consume/context-request.event.js'; +import type { UmbContextToken } from '../token/index.js'; +import { UMB_CONTEXT_REQUEST_EVENT_TYPE } from '../consume/context-request.event.js'; +import { UmbContextProvideEventImplementation } from './context-provide.event.js'; + +/** + * @class UmbContextBoundary + */ +export class UmbContextBoundary { + #eventTarget: EventTarget; + + #contextAlias: string; + + /** + * Creates an instance of UmbContextBoundary. + * @param {EventTarget} eventTarget - the host element for this context provider + * @param {string | UmbContextToken} contextIdentifier - a string or token to identify the context + * @param {*} instance - the instance to provide + * @memberof UmbContextBoundary + */ + constructor(eventTarget: EventTarget, contextIdentifier: string | UmbContextToken) { + this.#eventTarget = eventTarget; + + const idSplit = contextIdentifier.toString().split('#'); + this.#contextAlias = idSplit[0]; + + this.#eventTarget.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest); + } + + /** + * @private + * @param {UmbContextRequestEvent} event - the event to handle + * @memberof UmbContextBoundary + */ + #handleContextRequest = ((event: UmbContextRequestEvent): void => { + if (event.contextAlias !== this.#contextAlias) return; + + if (event.stopAtContextMatch) { + // Since the alias matches, we will stop it from bubbling further up. But we still allow it to ask the other Contexts of the element. Hence not calling `event.stopImmediatePropagation();` + event.stopPropagation(); + } + }) as EventListener; + + /** + * @memberof UmbContextBoundary + */ + public hostConnected(): void { + //this.hostElement.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, this.#handleContextRequest); + this.#eventTarget.dispatchEvent(new UmbContextProvideEventImplementation(this.#contextAlias)); + } + + /** + * @memberof UmbContextBoundary + */ + public hostDisconnected(): void {} + + destroy(): void { + (this.#eventTarget as unknown) = undefined; + } +} diff --git a/src/libs/context-api/provide/context-provider.ts b/src/libs/context-api/provide/context-provider.ts index 31f62cdc8e..56f231a2d2 100644 --- a/src/libs/context-api/provide/context-provider.ts +++ b/src/libs/context-api/provide/context-provider.ts @@ -1,6 +1,6 @@ import type { UmbContextRequestEvent } from '../consume/context-request.event.js'; import type { UmbContextToken } from '../token/index.js'; -import { UMB_CONTENT_REQUEST_EVENT_TYPE, UMB_DEBUG_CONTEXT_EVENT_TYPE } from '../consume/context-request.event.js'; +import { UMB_CONTEXT_REQUEST_EVENT_TYPE, UMB_DEBUG_CONTEXT_EVENT_TYPE } from '../consume/context-request.event.js'; import { UmbContextProvideEventImplementation } from './context-provide.event.js'; /** @@ -41,12 +41,12 @@ export class UmbContextProvider { @@ -68,7 +68,7 @@ export class UmbContextProvider { describe('Manifest without conditions', () => { - //let hostElement: UmbControllerHostElement; + let hostElement: UmbControllerHostElement; let extensionRegistry: UmbExtensionRegistry; let manifest: ManifestWithDynamicConditions; beforeEach(async () => { - //hostElement = await fixture(html``); + hostElement = await fixture(html``); extensionRegistry = new UmbExtensionRegistry(); manifest = { type: 'section', @@ -74,7 +74,7 @@ describe('UmbBaseExtensionController', () => { extensionRegistry.register(manifest); }); - /* + it('permits when there is no conditions', (done) => { const extensionController = new UmbTestExtensionController( hostElement, @@ -92,16 +92,15 @@ describe('UmbBaseExtensionController', () => { }, ); }); - */ }); describe('Manifest with empty conditions', () => { - //let hostElement: UmbControllerHostElement; + let hostElement: UmbControllerHostElement; let extensionRegistry: UmbExtensionRegistry; let manifest: ManifestWithDynamicConditions; beforeEach(async () => { - //hostElement = await fixture(html``); + hostElement = await fixture(html``); extensionRegistry = new UmbExtensionRegistry(); manifest = { type: 'section', @@ -113,7 +112,6 @@ describe('UmbBaseExtensionController', () => { extensionRegistry.register(manifest); }); - /* it('permits when there is empty conditions', (done) => { const extensionController = new UmbTestExtensionController( hostElement, @@ -124,7 +122,7 @@ describe('UmbBaseExtensionController', () => { if (extensionController.permitted) { expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1'); - // Also verifying that the promise gets resolved. + // Also verifying that the promise gets resolved. [NL] extensionController.asPromise().then(() => { done(); }); @@ -132,7 +130,6 @@ describe('UmbBaseExtensionController', () => { }, ); }); - */ }); describe('Manifest with valid conditions', () => { @@ -225,14 +222,14 @@ describe('UmbBaseExtensionController', () => { if (isPermitted) { count++; if (count === 1) { - // First time render, there is no conditions. + // First time render, there is no conditions. [NL] expect(extensionController.manifest?.weight).to.be.equal(2); expect(extensionController.manifest?.conditions?.length).to.be.equal(1); } else if (count === 2) { - // Second time render, there is conditions and weight is 22. + // Second time render, there is conditions and weight is 22. [NL] expect(extensionController.manifest?.weight).to.be.equal(22); expect(extensionController.manifest?.conditions?.length).to.be.equal(1); - // Check that the promise has been resolved for the first render to ensure timing is right. + // Check that the promise has been resolved for the first render to ensure timing is right. [NL] expect(initialPromiseResolved).to.be.true; done(); extensionController.destroy(); @@ -270,14 +267,14 @@ describe('UmbBaseExtensionController', () => { if (isPermitted) { count++; if (count === 1) { - // First time render, there is no conditions. + // First time render, there is no conditions. [NL] expect(extensionController.manifest?.weight).to.be.equal(3); expect(extensionController.manifest?.conditions?.length).to.be.equal(0); } else if (count === 2) { - // Second time render, there is conditions and weight is 33. + // Second time render, there is conditions and weight is 33. [NL] expect(extensionController.manifest?.weight).to.be.equal(33); expect(extensionController.manifest?.conditions?.length).to.be.equal(0); - // Check that the promise has been resolved for the first render to ensure timing is right. + // Check that the promise has been resolved for the first render to ensure timing is right. [NL] expect(initialPromiseResolved).to.be.true; done(); extensionController.destroy(); @@ -315,14 +312,14 @@ describe('UmbBaseExtensionController', () => { if (isPermitted) { count++; if (count === 1) { - // First time render, there is no conditions. + // First time render, there is no conditions. [NL] expect(extensionController.manifest?.weight).to.be.equal(4); expect(extensionController.manifest?.conditions?.length).to.be.equal(0); } else if (count === 2) { - // Second time render, there is conditions and weight is 33. + // Second time render, there is conditions and weight is updated. [NL] expect(extensionController.manifest?.weight).to.be.equal(44); expect(extensionController.manifest?.conditions?.length).to.be.equal(1); - // Check that the promise has been resolved for the first render to ensure timing is right. + // Check that the promise has been resolved for the first render to ensure timing is right. [NL] expect(initialPromiseResolved).to.be.true; done(); extensionController.destroy(); @@ -370,14 +367,14 @@ describe('UmbBaseExtensionController', () => { if (isPermitted) { count++; if (count === 1) { - // First time render, there is no conditions. + // First time render, there is no conditions. [NL] expect(extensionController.manifest?.weight).to.be.undefined; expect(extensionController.manifest?.conditions?.length).to.be.equal(0); } else if (count === 2) { - // Second time render, there is a matching kind and then weight is 123. + // Second time render, there is a matching kind and then weight is 123. [NL] expect(extensionController.manifest?.weight).to.be.equal(123); expect(extensionController.manifest?.conditions?.length).to.be.equal(0); - // Check that the promise has been resolved for the first render to ensure timing is right. + // Check that the promise has been resolved for the first render to ensure timing is right. [NL] expect(initialPromiseResolved).to.be.true; done(); extensionController.destroy(); @@ -433,7 +430,7 @@ describe('UmbBaseExtensionController', () => { 'Umb.Test.Section.1', () => { // This should not be called. - expect(true).to.be.false; + expect.fail('Callback should not be called when never permitted'); }, ); Promise.resolve().then(() => { @@ -451,7 +448,7 @@ describe('UmbBaseExtensionController', () => { 'Umb.Test.Section.1', () => { // This should not be called. - expect(true).to.be.false; + expect.fail('Callback should not be called when never permitted'); }, ); @@ -531,7 +528,7 @@ describe('UmbBaseExtensionController', () => { 'Umb.Test.Section.1', async () => { count++; - // We want the controller callback to first fire when conditions are initialized. + // We want the controller callback to first fire when conditions are initialized. [NL] expect(extensionController.manifest?.conditions?.length).to.be.equal(1); expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1'); if (count === 1) { @@ -596,29 +593,30 @@ describe('UmbBaseExtensionController', () => { 'Umb.Test.Section.1', async (isPermitted) => { count++; - // We want the controller callback to first fire when conditions are initialized. + // We want the controller callback to first fire when conditions are initialized. [NL] expect(extensionController.manifest?.conditions?.length).to.be.equal(2); expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1'); if (count === 1) { expect(isPermitted).to.be.true; expect(extensionController?.permitted).to.be.true; - // Hack to double check that its two conditions that make up the state: + // Hack to double check that its two conditions that make up the state: [NL] expect( extensionController.getUmbControllers((controller) => (controller as any).permitted).length, ).to.equal(2); } else if (count === 2) { expect(isPermitted).to.be.false; expect(extensionController?.permitted).to.be.false; - // Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good: + // Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good: [NL] expect( extensionController.getUmbControllers((controller) => (controller as any).permitted).length, ).to.equal(1); // Then we are done: extensionController.destroy(); // End this test. - setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.) + setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.) [NL] } else if (count === 5) { - expect(false).to.be.true; // This should not be called. + // This should not be called. + expect.fail('Callback should not be called when never permitted'); } }, ); diff --git a/src/libs/extension-api/controller/base-extension-initializer.controller.ts b/src/libs/extension-api/controller/base-extension-initializer.controller.ts index cf5fcf63b7..52cff378b7 100644 --- a/src/libs/extension-api/controller/base-extension-initializer.controller.ts +++ b/src/libs/extension-api/controller/base-extension-initializer.controller.ts @@ -8,7 +8,10 @@ import type { ManifestWithDynamicConditions, UmbExtensionRegistry, } from '@umbraco-cms/backoffice/extension-api'; -import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import { jsonStringComparison, type UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; + +const observeConditionsCtrlAlias = Symbol(); +const observeExtensionsCtrlAlias = Symbol(); /** * This abstract Controller holds the core to manage a single Extension. @@ -73,8 +76,6 @@ export abstract class UmbBaseExtensionInitializer< this.#manifestObserver = this.observe( this.#extensionRegistry.byAlias(this.#alias), (extensionManifest) => { - this.#clearPermittedState(); - this.#manifest = extensionManifest; if (extensionManifest) { if (extensionManifest.overwrites) { if (typeof extensionManifest.overwrites === 'string') { @@ -83,13 +84,15 @@ export abstract class UmbBaseExtensionInitializer< this.#overwrites = extensionManifest.overwrites; } } - this.#gotManifest(); + this.#gotManifest(extensionManifest); } else { + this.#manifest = undefined; + this.#clearPermittedState(); this.#overwrites = []; this.#cleanConditions(); } }, - '_observeExtensionManifest', + observeExtensionsCtrlAlias, ); } @@ -107,19 +110,22 @@ export abstract class UmbBaseExtensionInitializer< if (this.#conditionControllers === undefined || this.#conditionControllers.length === 0) return; this.#conditionControllers.forEach((controller) => controller.destroy()); this.#conditionControllers = []; - this.removeUmbControllerByAlias('_observeConditions'); + this.removeUmbControllerByAlias(observeConditionsCtrlAlias); } - #gotManifest() { - if (!this.#manifest) return; - const conditionConfigs = this.#manifest.conditions ?? []; + #gotManifest(manifest: ManifestType) { + const conditionConfigs = manifest.conditions ?? []; + const oldManifest = this.#manifest; + this.#manifest = manifest; - // As conditionConfigs might have been configured as something else than an array, then we ignorer them. + /* + // As conditionConfigs might have been configured as something else than an array, then we ignorer them. [NL] if (conditionConfigs.length === 0) { this.#cleanConditions(); this.#onConditionsChangedCallback(); return; } + */ const conditionAliases = conditionConfigs .map((condition) => condition.alias) @@ -130,32 +136,39 @@ export abstract class UmbBaseExtensionInitializer< this.#conditionControllers = this.#conditionControllers.filter((current) => { const continueExistence = conditionConfigs.find((config) => config === current.config); if (!continueExistence) { - // Destroy condition that is no longer needed. + // Destroy condition that is no longer needed. [NL] current.destroy(); } return continueExistence; }); - // Check if there was no change in conditions: - // First check if any got removed(old amount equal controllers after clean-up) - // && check if any new is about to be added(old equal new amount): - const noChangeInConditions = - oldAmountOfControllers === this.#conditionControllers.length && - oldAmountOfControllers === conditionConfigs.length; - if (conditionConfigs.length > 0) { - // Observes the conditions and initialize as they come in. + // Observes the conditions and initialize as they come in. [NL] this.observe( this.#extensionRegistry.byTypeAndAliases('condition', conditionAliases), this.#gotConditions, - '_observeConditions', + observeConditionsCtrlAlias, ); } else { - this.removeUmbControllerByAlias('_observeConditions'); + this.removeUmbControllerByAlias(observeConditionsCtrlAlias); } - if (noChangeInConditions) { - // There was not change in the amount of conditions, but the manifest was changed, this means this.#isPermitted is set to undefined and this will always fire the callback: + // If permitted we want to fire an update because we got a new manifest. [NL] + if (this.#isPermitted) { + // Check if there was no change in conditions: + // First check if any got removed(old amount equal controllers after clean-up) + // && check if any new is about to be added(old equal new amount): [NL] + // The reason for this is because we will get an update via the code above if there is a change in conditions. But if not we will trigger it here [NL] + const noChangeInConditions = + oldAmountOfControllers === this.#conditionControllers.length && + oldAmountOfControllers === conditionConfigs.length; + if (noChangeInConditions) { + if (jsonStringComparison(oldManifest, manifest) === false) { + // There was not change in the amount of conditions, but the manifest was changed, this means this.#isPermitted is set to undefined and this will always fire the callback: [NL] + this.#onPermissionChanged?.(this.#isPermitted, this as any); + } + } + } else { this.#onConditionsChangedCallback(); } } @@ -189,9 +202,9 @@ export abstract class UmbBaseExtensionInitializer< newConditionControllers .filter((x) => x !== undefined) .forEach((emerging) => { - // TODO: All of this could use a refactor at one point, when someone is fresh in their mind. - // Niels Notes: Current problem being that we are not aware about what is in the making, so we don't know if we end up creating the same condition multiple times. - // Because it took some time to create the conditions, it maybe have already gotten created by another cycle, so lets test again. + // TODO: All of this could use a refactor at one point, when someone is fresh in their mind. [NL] + // Niels Notes: Current problem being that we are not aware about what is in the making, so we don't know if we end up creating the same condition multiple times. [NL] + // Because it took some time to create the conditions, it maybe have already gotten created by another cycle, so lets test again. [NL] const existing = this.#conditionControllers.find((existing) => existing.config === emerging?.config); if (!existing) { this.#conditionControllers.push(emerging!); @@ -200,7 +213,7 @@ export abstract class UmbBaseExtensionInitializer< } }); - // If a change to amount of condition controllers, this will make sure that when new conditions are added, the callback is fired, so the extensions can be re-evaluated, starting out as bad. + // If a change to amount of condition controllers, this will make sure that when new conditions are added, the callback is fired, so the extensions can be re-evaluated, starting out as bad. [NL] if (oldLength !== this.#conditionControllers.length) { this.#onConditionsChangedCallback(); } @@ -230,14 +243,19 @@ export abstract class UmbBaseExtensionInitializer< #conditionsAreInitialized() { // Not good if we don't have a manifest. - // Only good if conditions of manifest is equal to the amount of condition controllers (one for each condition). + // Only good if conditions of manifest is equal to the amount of condition controllers (one for each condition). [NL] return ( this.#manifest !== undefined && this.#conditionControllers.length === (this.#manifest.conditions ?? []).length ); } #onConditionsChangedCallback = async () => { - // We will collect old value here, but we need to re-collect it after a async method have been called, as it could have changed in the mean time. + if (this.#manifest === undefined) { + // This is cause by this controller begin destroyed in the mean time. [NL] + // When writing this the only plausible case is a call from the conditionController to the onChange callback. + return; + } + // We will collect old value here, but we need to re-collect it after a async method have been called, as it could have changed in the mean time. [NL] let oldValue = this.#isPermitted ?? false; // Find a condition that is not permitted (Notice how no conditions, means that this extension is permitted) @@ -250,13 +268,13 @@ export abstract class UmbBaseExtensionInitializer< if (isPositive === true) { if (this.#isPermitted !== true) { const newPermission = await this._conditionsAreGood(); - // Only set new permission if we are still positive, otherwise it means that we have been destroyed in the mean time. + // Only set new permission if we are still positive, otherwise it means that we have been destroyed in the mean time. [NL] if (newPermission === false || this._isConditionsPositive === false) { // Then we need to revert the above work: this._conditionsAreBad(); return; } - // We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time. + // We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time. [NL] oldValue = this.#isPermitted ?? false; this.#isPermitted = newPermission; } @@ -264,11 +282,11 @@ export abstract class UmbBaseExtensionInitializer< // Clean up: await this._conditionsAreBad(); - // Only continue if we are still negative, otherwise it means that something changed in the mean time. + // Only continue if we are still negative, otherwise it means that something changed in the mean time. [NL] if (this._isConditionsPositive === true) { return; } - // We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time. + // We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time. [NL] oldValue = this.#isPermitted ?? false; this.#isPermitted = false; } @@ -286,7 +304,7 @@ export abstract class UmbBaseExtensionInitializer< protected abstract _conditionsAreBad(): Promise; public equal(otherClass: UmbBaseExtensionInitializer | undefined): boolean { - return otherClass?.manifest === this.manifest; + return otherClass?.manifest === this.#manifest; } /* @@ -296,6 +314,7 @@ export abstract class UmbBaseExtensionInitializer< } */ + /* public override hostDisconnected(): void { super.hostDisconnected(); this._isConditionsPositive = false; @@ -305,6 +324,7 @@ export abstract class UmbBaseExtensionInitializer< this.#onPermissionChanged?.(false, this as unknown as SubClassType); } } + */ #clearPermittedState() { if (this.#isPermitted === true) { @@ -318,7 +338,7 @@ export abstract class UmbBaseExtensionInitializer< if (!this.#extensionRegistry) return; this.#manifest = undefined; this.#promiseResolvers = []; - this.#clearPermittedState(); // This fires the callback as not permitted, if it was permitted before. + this.#clearPermittedState(); // This fires the callback as not permitted, if it was permitted before. [NL] this.#isPermitted = undefined; this._isConditionsPositive = false; this.#overwrites = []; diff --git a/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts b/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts index abcf3326db..2b12443731 100644 --- a/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts +++ b/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts @@ -13,6 +13,8 @@ import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} class UmbTestExtensionController extends UmbBaseExtensionInitializer { + testInsidesIsDestroyed?: boolean; + constructor( host: UmbControllerHost, extensionRegistry: UmbExtensionRegistry, @@ -28,7 +30,8 @@ class UmbTestExtensionController extends UmbBaseExtensionInitializer { } protected async _conditionsAreBad() { - // Destroy the element/class. + // Destroy the element/class. (or in the case of this test, then we just set a flag) [NL] + this.testInsidesIsDestroyed = true; } } @@ -111,7 +114,7 @@ describe('UmbBaseExtensionsController', () => { it('exposes both manifests', (done) => { let count = 0; - const extensionController = new UmbTestExtensionsController( + const extensionsInitController = new UmbTestExtensionsController( hostElement, testExtensionRegistry, 'extension-type', @@ -120,7 +123,7 @@ describe('UmbBaseExtensionsController', () => { count++; if (count === 1) { expect(permitted.length).to.eq(2); - extensionController.destroy(); + extensionsInitController.destroy(); } else if (count === 2) { done(); } @@ -138,7 +141,7 @@ describe('UmbBaseExtensionsController', () => { testExtensionRegistry.register(manifestExtra); let count = 0; - const extensionController = new UmbTestExtensionsController( + const extensionsInitController = new UmbTestExtensionsController( hostElement, testExtensionRegistry, ['extension-type', 'extension-type-extra'], @@ -151,9 +154,9 @@ describe('UmbBaseExtensionsController', () => { expect(permitted[1].alias).to.eq('Umb.Test.Extension.B'); expect(permitted[2].alias).to.eq('Umb.Test.Extension.Extra'); - extensionController.destroy(); + extensionsInitController.destroy(); } else if (count === 2) { - // Cause we destroyed there will be a last call to reset permitted controllers: + // Cause we destroyed there will be a last call to reset permitted controllers: [NL] expect(permitted.length).to.eq(0); done(); } @@ -189,7 +192,7 @@ describe('UmbBaseExtensionsController', () => { it('exposes just one manifests', (done) => { let count = 0; - const extensionController = new UmbTestExtensionsController( + const extensionsInitController = new UmbTestExtensionsController( hostElement, testExtensionRegistry, 'extension-type', @@ -197,17 +200,17 @@ describe('UmbBaseExtensionsController', () => { (permitted) => { count++; if (count === 1) { - // Still just equal one, as the second one overwrites the first one. + // Still just equal one, as the second one overwrites the first one. [NL] expect(permitted.length).to.eq(1); expect(permitted[0].alias).to.eq('Umb.Test.Extension.B'); - // lets remove the overwriting extension to see the original coming back. + // lets remove the overwriting extension to see the original coming back. [NL] testExtensionRegistry.unregister('Umb.Test.Extension.B'); } else if (count === 2) { expect(permitted.length).to.eq(1); expect(permitted[0].alias).to.eq('Umb.Test.Extension.A'); done(); - extensionController.destroy(); + extensionsInitController.destroy(); } }, ); @@ -255,7 +258,8 @@ describe('UmbBaseExtensionsController', () => { it('exposes only the overwriting manifest', (done) => { let count = 0; - const extensionController = new UmbTestExtensionsController( + let lastPermitted: PermittedControllerType[] = []; + const extensionsInitController = new UmbTestExtensionsController( hostElement, testExtensionRegistry, 'extension-type', @@ -263,24 +267,30 @@ describe('UmbBaseExtensionsController', () => { (permitted) => { count++; if (count === 1) { - // Still just equal one, as the second one overwrites the first one. + // Still just equal one, as the second one overwrites the first one. [NL] expect(permitted.length).to.eq(1); expect(permitted[0].alias).to.eq('Umb.Test.Extension.B'); - // lets remove the overwriting extension to see the original coming back. + // lets remove the overwriting extension to see the original coming back. [NL] testExtensionRegistry.unregister('Umb.Test.Extension.B'); } else if (count === 2) { expect(permitted.length).to.eq(1); expect(permitted[0].alias).to.eq('Umb.Test.Extension.A'); - extensionController.destroy(); + // CHecks that the controller that got overwritten is destroyed. [NL] + expect(lastPermitted[0].testInsidesIsDestroyed).to.be.true; + // Then continue the test and destroy the initializer. [NL] + extensionsInitController.destroy(); + // And then checks that the controller is destroyed. [NL] + expect(permitted[0].testInsidesIsDestroyed).to.be.true; } else if (count === 3) { - // Expect that destroy will only result in one last callback with no permitted controllers. + // Expect that destroy will only result in one last callback with no permitted controllers. [NL] expect(permitted.length).to.eq(0); - Promise.resolve().then(() => done()); // This wrap is to enable the test to detect if more callbacks are fired. + Promise.resolve().then(() => done()); // This wrap is to enable the test to detect if more callbacks are fired. [NL] } else if (count === 4) { - // This should not happen, we do only want one last callback when destroyed. + // This should not happen, we do only want one last callback when destroyed. [NL] expect(false).to.eq(true); } + lastPermitted = permitted; }, ); }); @@ -308,7 +318,7 @@ describe('UmbBaseExtensionsController', () => { ], }; - // Register opposite order, to ensure B is there when A comes around. A fix to be able to test this. Cause a late registration of B would not cause a change that is test able. + // Register opposite order, to ensure B is there when A comes around. A fix to be able to test this. Cause a late registration of B would not cause a change that is test able. [NL] testExtensionRegistry.register(manifestB); testExtensionRegistry.register(manifestA); testExtensionRegistry.register({ @@ -329,7 +339,7 @@ describe('UmbBaseExtensionsController', () => { it('exposes only the original manifest', (done) => { let count = 0; - const extensionController = new UmbTestExtensionsController( + const extensionsInitController = new UmbTestExtensionsController( hostElement, testExtensionRegistry, 'extension-type', @@ -338,11 +348,11 @@ describe('UmbBaseExtensionsController', () => { count++; if (count === 1) { - // First callback gives just one. We need to make a feature to gather changes to only reply after a computation cycle if we like to avoid this. + // First callback gives just one. We need to make a feature to gather changes to only reply after a computation cycle if we like to avoid this. [NL] expect(permitted.length).to.eq(1); expect(permitted[0].alias).to.eq('Umb.Test.Extension.A'); done(); - extensionController.destroy(); + extensionsInitController.destroy(); } }, ); diff --git a/src/libs/observable-api/utils/push-at-to-unique-array.function.ts b/src/libs/observable-api/utils/push-at-to-unique-array.function.ts index a46b329639..f31ab8c332 100644 --- a/src/libs/observable-api/utils/push-at-to-unique-array.function.ts +++ b/src/libs/observable-api/utils/push-at-to-unique-array.function.ts @@ -5,6 +5,7 @@ * @param {T} entry - The object to insert or replace with. * @param {getUniqueMethod: (entry: T) => unknown} [getUniqueMethod] - Method to get the unique value of an entry. * @description - Append or replaces an item of an Array. + * @returns {T[]} - Returns a new array with the updated entry. * @example Example append new entry for a Array. Where the key is unique and the item will be updated if matched with existing. * const entry = {key: 'myKey', value: 'myValue'}; * const newDataSet = pushToUniqueArray([], entry, x => x.key === key, 1); diff --git a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts index 10eb00ca0a..315fd1ff4d 100644 --- a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts +++ b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts @@ -1,7 +1,6 @@ import { manifests as workspaceViewManifests } from './views/manifests.js'; import { UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_ALIAS } from './index.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ ...workspaceViewManifests, diff --git a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts index ba7238c442..3bdf1271b9 100644 --- a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts +++ b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_ALIAS } from '../index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import type { ManifestWorkspaceView } from '@umbraco-cms/backoffice/workspace'; export const workspaceViews: Array = [ diff --git a/src/packages/block/block-grid/components/block-grid-areas-container/block-grid-areas-container.element.ts b/src/packages/block/block-grid/components/block-grid-areas-container/block-grid-areas-container.element.ts index 1f93598b92..adb7ceea0d 100644 --- a/src/packages/block/block-grid/components/block-grid-areas-container/block-grid-areas-container.element.ts +++ b/src/packages/block/block-grid/components/block-grid-areas-container/block-grid-areas-container.element.ts @@ -1,7 +1,8 @@ +import type { UmbBlockGridTypeAreaType } from '../../types.js'; +import { UMB_BLOCK_GRID_ENTRY_CONTEXT } from '../../context/index.js'; import { UMB_BLOCK_GRID_MANAGER_CONTEXT } from '../../context/block-grid-manager.context-token.js'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { css, customElement, html, repeat, state } from '@umbraco-cms/backoffice/external/lit'; -import { UMB_BLOCK_GRID_ENTRY_CONTEXT, type UmbBlockGridTypeAreaType } from '@umbraco-cms/backoffice/block-grid'; import '../block-grid-entries/index.js'; /** diff --git a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts index 82e3f4333f..06156a1b8f 100644 --- a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts +++ b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts @@ -1,4 +1,6 @@ import { UMB_BLOCK_GRID_ENTRY_CONTEXT } from '../../context/block-grid-entry.context-token.js'; +import type { UmbBlockGridWorkspaceOriginData } from '../../workspace/block-grid-workspace.modal-token.js'; +import { UMB_BLOCK_GRID_ENTRIES_CONTEXT } from '../../context/block-grid-entries.context-token.js'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; @@ -32,6 +34,8 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { #blockContext?: typeof UMB_BLOCK_GRID_ENTRY_CONTEXT.TYPE; #workspaceContext?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; #contentKey?: string; + #parentUnique?: string | null; + #areaKey?: string | null; @property({ attribute: false }) config?: UmbBlockEditorCustomViewConfiguration; @@ -71,6 +75,10 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { 'observeContentKey', ); }); + this.consumeContext(UMB_BLOCK_GRID_ENTRIES_CONTEXT, (entriesContext) => { + this.#parentUnique = entriesContext.getParentUnique(); + this.#areaKey = entriesContext.getAreaKey(); + }); new UmbExtensionApiInitializer( this, umbExtensionsRegistry, @@ -79,7 +87,15 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { (permitted, ctrl) => { const context = ctrl.api as typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; if (permitted && context) { + // Risky business, cause here we are lucky that it seems to be consumed and set before this is called and there for this is acceptable for now. [NL] + if (this.#parentUnique === undefined || this.#areaKey === undefined) { + throw new Error('Parent unique and area key must be defined'); + } this.#workspaceContext = context; + context.setOriginData({ + areaKey: this.#areaKey, + parentUnique: this.#parentUnique, + } as UmbBlockGridWorkspaceOriginData); this.#workspaceContext.establishLiveSync(); this.#load(); @@ -277,6 +293,19 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { margin-right: var(--uui-size-1); } + #info { + display: flex; + flex-direction: column; + align-items: start; + justify-content: center; + height: 100%; + padding-left: var(--uui-size-2, 6px); + } + + #name { + font-weight: 700; + } + :host(:not([disabled])) #open-part:hover #icon { color: var(--uui-color-interactive-emphasis); } @@ -294,6 +323,7 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { #inside { position: relative; display: block; + padding: calc(var(--uui-size-layout-1)); } `, ]; diff --git a/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index 6c4a76df0e..7e763cf82c 100644 --- a/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -1,5 +1,6 @@ import { UmbBlockGridEntriesContext } from '../../context/block-grid-entries.context.js'; import type { UmbBlockGridEntryElement } from '../block-grid-entry/index.js'; +import type { UmbBlockGridLayoutModel } from '../../types.js'; import { getAccumulatedValueOfIndex, getInterpolatedIndexOfPositionInWeightMap, @@ -16,7 +17,6 @@ import { type UmbFormControlValidatorConfig, } from '@umbraco-cms/backoffice/validation'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; -import type { UmbBlockGridLayoutModel } from '@umbraco-cms/backoffice/block-grid'; /** * Notice this utility method is not really shareable with others as it also takes areas into account. [NL] diff --git a/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 4e39f774d5..e291af4aeb 100644 --- a/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -1,10 +1,11 @@ import { UmbBlockGridEntryContext } from '../../context/block-grid-entry.context.js'; -import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbBlockGridLayoutModel } from '../../types.js'; +import { UMB_BLOCK_GRID } from '../../constants.js'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { html, css, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils'; -import { UMB_BLOCK_GRID, type UmbBlockGridLayoutModel } from '@umbraco-cms/backoffice/block-grid'; import '../block-grid-block-inline/index.js'; import '../block-grid-block/index.js'; @@ -414,8 +415,8 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper .unpublished=${!this._exposed} .config=${this._blockViewProps.config} .content=${this._blockViewProps.content} - .settings=${this._blockViewProps.settings} - ${umbDestroyOnDisconnect()}>`; + .settings=${this._blockViewProps.settings}>`; + //TODO: investigate if we should have ${umbDestroyOnDisconnect()} here. Note how it works for drag n' drop in grid between areas and areas-root } #renderRefBlock() { @@ -426,8 +427,8 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper .unpublished=${!this._exposed} .config=${this._blockViewProps.config} .content=${this._blockViewProps.content} - .settings=${this._blockViewProps.settings} - ${umbDestroyOnDisconnect()}>`; + .settings=${this._blockViewProps.settings}>`; + //TODO: investigate if we should have ${umbDestroyOnDisconnect()} here. Note how it works for drag n' drop in grid between areas and areas-root } #renderBlock() { @@ -598,15 +599,15 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper inset: 0; border: 1px solid transparent; border-radius: var(--uui-border-radius); - box-shadow: - 0 0 0 1px rgba(255, 255, 255, 0.7), - inset 0 0 0 1px rgba(255, 255, 255, 0.7); transition: border-color 240ms ease-in; } :host(:hover):not(:drop)::after { display: block; border-color: var(--uui-color-interactive-emphasis); + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.7), + inset 0 0 0 1px rgba(255, 255, 255, 0.7); } :host([drag-placeholder])::after { diff --git a/src/packages/block/block-grid/components/block-scale-handler/block-scale-handler.element.ts b/src/packages/block/block-grid/components/block-scale-handler/block-scale-handler.element.ts index b8c25d1732..31c59b5002 100644 --- a/src/packages/block/block-grid/components/block-scale-handler/block-scale-handler.element.ts +++ b/src/packages/block/block-grid/components/block-scale-handler/block-scale-handler.element.ts @@ -1,7 +1,6 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { html, css, customElement } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; -import '../block-grid-block/index.js'; /** * @element umb-block-scale-handler diff --git a/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/packages/block/block-grid/context/block-grid-entries.context.ts index b69f1a2853..1850c2dd14 100644 --- a/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -99,6 +99,10 @@ export class UmbBlockGridEntriesContext this.#catalogueModal.setUniquePathValue('parentUnique', pathFolderName(contentKey ?? 'null')); } + getParentUnique(): string | null | undefined { + return this.#parentUnique; + } + setAreaKey(areaKey: string | null) { this.#areaKey = areaKey; this.#workspaceModal.setUniquePathValue('areaKey', areaKey ?? 'null'); @@ -110,6 +114,10 @@ export class UmbBlockGridEntriesContext // If not, we want to set the layoutDataPath to a base one. } + getAreaKey(): string | null | undefined { + return this.#areaKey; + } + setLayoutColumns(columns: number | undefined) { this.#layoutColumns.setValue(columns); } @@ -157,7 +165,11 @@ export class UmbBlockGridEntriesContext blocks: this.#allowedBlockTypes.getValue(), blockGroups: this._manager.getBlockGroups() ?? [], openClipboard: routingInfo.view === 'clipboard', - originData: { index: index, areaKey: this.#areaKey, parentUnique: this.#parentUnique }, + originData: { + index: index, + areaKey: this.#areaKey, + parentUnique: this.#parentUnique, + } as UmbBlockGridWorkspaceOriginData, createBlockInWorkspace: this._manager.getInlineEditingMode() === false, }, }; @@ -195,7 +207,12 @@ export class UmbBlockGridEntriesContext data: { entityType: 'block', preset: {}, - originData: { areaKey: this.#areaKey, parentUnique: this.#parentUnique, baseDataPath: this._dataPath }, + originData: { + index: -1, + areaKey: this.#areaKey, + parentUnique: this.#parentUnique, + baseDataPath: this._dataPath, + } as UmbBlockGridWorkspaceOriginData, }, modal: { size: 'medium' }, }; diff --git a/src/packages/block/block-grid/context/block-grid-manager.context.ts b/src/packages/block/block-grid/context/block-grid-manager.context.ts index 2f411cfd2b..cd4b63e0a5 100644 --- a/src/packages/block/block-grid/context/block-grid-manager.context.ts +++ b/src/packages/block/block-grid/context/block-grid-manager.context.ts @@ -1,18 +1,19 @@ import type { UmbBlockGridLayoutModel, UmbBlockGridTypeModel } from '../types.js'; import type { UmbBlockGridWorkspaceOriginData } from '../index.js'; import { - UmbArrayState, - UmbBooleanState, appendToFrozenArray, pushAtToUniqueArray, + UmbArrayState, + UmbBooleanState, } from '@umbraco-cms/backoffice/observable-api'; -import { removeLastSlashFromPath, transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils'; +import { UmbBlockManagerContext } from '@umbraco-cms/backoffice/block'; import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; -import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; -import { type UmbBlockDataModel, UmbBlockManagerContext } from '@umbraco-cms/backoffice/block'; +import type { UmbBlockDataModel } from '@umbraco-cms/backoffice/block'; import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; +import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; export const UMB_BLOCK_GRID_DEFAULT_LAYOUT_STYLESHEET = '/umbraco/backoffice/css/umbraco-blockgridlayout.css'; @@ -34,7 +35,9 @@ export class UmbBlockGridManagerContext< } #initAppUrl: Promise; - #appUrl?: string; + + #serverUrl?: string; + #blockGroups = new UmbArrayState(>[], (x) => x.key); public readonly blockGroups = this.#blockGroups.asObservable(); @@ -45,7 +48,8 @@ export class UmbBlockGridManagerContext< if (layoutStylesheet) { // Cause we await initAppUrl in setting the _editorConfiguration, we can trust the appUrl begin here. - return removeLastSlashFromPath(this.#appUrl!) + transformServerPathToClientPath(layoutStylesheet); + const url = new URL(transformServerPathToClientPath(layoutStylesheet), this.#serverUrl); + return url.href; } return undefined; }); @@ -85,7 +89,7 @@ export class UmbBlockGridManagerContext< super(host); this.#initAppUrl = this.getContext(UMB_APP_CONTEXT).then((appContext) => { - this.#appUrl = appContext.getServerUrl() + appContext.getBackofficePath(); + this.#serverUrl = appContext.getServerUrl(); }); } diff --git a/src/packages/block/block-grid/workspace/views/manifests.ts b/src/packages/block/block-grid/workspace/views/manifests.ts index 8722b9f764..e14984f6c2 100644 --- a/src/packages/block/block-grid/workspace/views/manifests.ts +++ b/src/packages/block/block-grid/workspace/views/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_GRID_TYPE_WORKSPACE_ALIAS } from '../index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts index f23b039e1b..d3b58036f7 100644 --- a/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts +++ b/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts @@ -300,22 +300,24 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper } #renderBlock() { - return html` - ${this._inlineEditingMode ? this.#renderInlineBlock() : this.#renderRefBlock()} - - ${this.#renderEditContentAction()} ${this.#renderEditSettingsAction()} ${this.#renderDeleteAction()} - - ${!this._showContentEdit && this._contentInvalid - ? html`!` - : nothing} - `; + return this.contentKey && this._contentTypeAlias + ? html` + ${this._inlineEditingMode ? this.#renderInlineBlock() : this.#renderRefBlock()} + + ${this.#renderEditContentAction()} ${this.#renderEditSettingsAction()} ${this.#renderDeleteAction()} + + ${!this._showContentEdit && this._contentInvalid + ? html`!` + : nothing} + ` + : nothing; } #renderEditContentAction() { diff --git a/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts b/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts index b0685a4828..f6749f63d3 100644 --- a/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts +++ b/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts @@ -271,6 +271,19 @@ export class UmbInlineListBlockElement extends UmbLitElement { margin-right: var(--uui-size-1); } + #info { + display: flex; + flex-direction: column; + align-items: start; + justify-content: center; + height: 100%; + padding-left: var(--uui-size-2, 6px); + } + + #name { + font-weight: 700; + } + :host(:not([disabled])) #open-part:hover #icon { color: var(--uui-color-interactive-emphasis); } diff --git a/src/packages/block/block-list/workspace/views/manifests.ts b/src/packages/block/block-list/workspace/views/manifests.ts index 04d75d39ab..a019927e9c 100644 --- a/src/packages/block/block-list/workspace/views/manifests.ts +++ b/src/packages/block/block-list/workspace/views/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_LIST_TYPE_WORKSPACE_ALIAS } from '../index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/block/block-rte/workspace/views/manifests.ts b/src/packages/block/block-rte/workspace/views/manifests.ts index 5b8abf2041..f075a1e992 100644 --- a/src/packages/block/block-rte/workspace/views/manifests.ts +++ b/src/packages/block/block-rte/workspace/views/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_RTE_TYPE_WORKSPACE_ALIAS } from '../index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/block/block-type/workspace/block-type-workspace.context.ts b/src/packages/block/block-type/workspace/block-type-workspace.context.ts index 1d17eb81ec..4b99ed1c3b 100644 --- a/src/packages/block/block-type/workspace/block-type-workspace.context.ts +++ b/src/packages/block/block-type/workspace/block-type-workspace.context.ts @@ -54,7 +54,7 @@ export class UmbBlockTypeWorkspaceContext = [ { diff --git a/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts b/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts index 2c2b9da88c..4eaa8b431c 100644 --- a/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts +++ b/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts @@ -4,9 +4,11 @@ import { UmbDataPathPropertyValueQuery, } from '@umbraco-cms/backoffice/validation'; +const ctrlAlias = Symbol(); + export class UmbBlockElementValuesDataValidationPathTranslator extends UmbAbstractArrayValidationPathTranslator { constructor(host: UmbControllerHost) { - super(host, '$.values[', UmbDataPathPropertyValueQuery); + super(host, '$.values[', UmbDataPathPropertyValueQuery, ctrlAlias); } getDataFromIndex(index: number) { diff --git a/src/packages/block/block/workspace/block-workspace.context.ts b/src/packages/block/block/workspace/block-workspace.context.ts index 31cf151c32..d7f83662fe 100644 --- a/src/packages/block/block/workspace/block-workspace.context.ts +++ b/src/packages/block/block/workspace/block-workspace.context.ts @@ -16,13 +16,12 @@ import { observeMultiple, } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UMB_MODAL_CONTEXT, type UmbModalContext } from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { decodeFilePath, UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; import { UMB_BLOCK_ENTRIES_CONTEXT, UMB_BLOCK_MANAGER_CONTEXT, type UmbBlockWorkspaceOriginData, - type UmbBlockWorkspaceData, UMB_BLOCK_ENTRY_CONTEXT, } from '@umbraco-cms/backoffice/block'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; @@ -40,7 +39,11 @@ export class UmbBlockWorkspaceContext; + #originData?: UmbBlockWorkspaceOriginData; + // Set the origin data for this workspace. Example used by inline editing which setups the workspace context it self. + setOriginData(data: UmbBlockWorkspaceOriginData) { + this.#originData = data; + } #retrieveModalContext; #entityType: string; @@ -80,7 +83,7 @@ export class UmbBlockWorkspaceContext { - this.#modalContext = context as any; + this.#originData = context?.data.originData; context.onSubmit().catch(this.#modalRejected); }).asPromise(); @@ -176,7 +179,7 @@ export class UmbBlockWorkspaceContext { if (layoutData) { - this.#blockManager?.setOneLayout( - layoutData, - this.#modalContext?.data.originData as UmbBlockWorkspaceOriginData, - ); + if (initialLayoutSet) { + initialLayoutSet = false; + return; + } + this.#blockManager?.setOneLayout(layoutData, this.#originData); } }, 'observeThisLayout', @@ -430,7 +432,7 @@ export class UmbBlockWorkspaceContext = [ { diff --git a/src/packages/language/collection/views/table/column-layouts/boolean/language-table-boolean-column-layout.element.ts b/src/packages/core/collection/components/boolean-table-column-view/boolean-table-column-view.element.ts similarity index 62% rename from src/packages/language/collection/views/table/column-layouts/boolean/language-table-boolean-column-layout.element.ts rename to src/packages/core/collection/components/boolean-table-column-view/boolean-table-column-view.element.ts index 479bc70b2e..856e202deb 100644 --- a/src/packages/language/collection/views/table/column-layouts/boolean/language-table-boolean-column-layout.element.ts +++ b/src/packages/core/collection/components/boolean-table-column-view/boolean-table-column-view.element.ts @@ -1,8 +1,8 @@ import { html, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -@customElement('umb-language-table-boolean-column-layout') -export class UmbLanguageTableBooleanColumnLayoutElement extends UmbLitElement { +@customElement('umb-boolean-table-column-view') +export class UmbBooleanTableColumnViewElement extends UmbLitElement { @property({ attribute: false }) value = false; @@ -13,6 +13,6 @@ export class UmbLanguageTableBooleanColumnLayoutElement extends UmbLitElement { declare global { interface HTMLElementTagNameMap { - 'umb-language-table-boolean-column-layout': UmbLanguageTableBooleanColumnLayoutElement; + 'umb-boolean-table-column-view': UmbBooleanTableColumnViewElement; } } diff --git a/src/packages/core/collection/components/index.ts b/src/packages/core/collection/components/index.ts index 7abcffff90..a440030db1 100644 --- a/src/packages/core/collection/components/index.ts +++ b/src/packages/core/collection/components/index.ts @@ -1,3 +1,4 @@ +import './boolean-table-column-view/boolean-table-column-view.element.js'; import './collection-action-bundle.element.js'; import './collection-filter-field.element.js'; import './collection-selection-actions.element.js'; diff --git a/src/packages/core/collection/components/pagination/collection-pagination.element.ts b/src/packages/core/collection/components/pagination/collection-pagination.element.ts index 7ee0688115..8bd77abf64 100644 --- a/src/packages/core/collection/components/pagination/collection-pagination.element.ts +++ b/src/packages/core/collection/components/pagination/collection-pagination.element.ts @@ -63,6 +63,10 @@ export class UmbCollectionPaginationElement extends UmbLitElement { UmbTextStyles, css` :host { + display: contents; + } + + uui-pagination { display: block; margin-top: var(--uui-size-layout-1); } diff --git a/src/packages/core/collection/default/collection-default.element.ts b/src/packages/core/collection/default/collection-default.element.ts index c1d0f54116..4350fdf0a7 100644 --- a/src/packages/core/collection/default/collection-default.element.ts +++ b/src/packages/core/collection/default/collection-default.element.ts @@ -113,11 +113,11 @@ export class UmbCollectionDefaultElement extends UmbLitElement { } #router { - display: none; + visibility: hidden; } .has-items #router { - display: block; + visibility: visible; } #empty-state { diff --git a/src/packages/core/content-type/index.ts b/src/packages/core/content-type/index.ts index 79f883296b..2a3cd65724 100644 --- a/src/packages/core/content-type/index.ts +++ b/src/packages/core/content-type/index.ts @@ -1,7 +1,12 @@ -export * from './components/index.js'; export * from './composition/index.js'; export * from './modals/index.js'; export * from './repository/index.js'; export * from './structure/index.js'; export * from './workspace/index.js'; export type * from './types.js'; + +/** + * @deprecated Use `UmbPropertyTypeBasedPropertyElement` from `@umbraco-cms/backoffice/content` instead. + * Export will be removed in version 17. + */ +export { UmbPropertyTypeBasedPropertyElement } from '../content/components/property-type-based-property/property-type-based-property.element.js'; diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts index ead29d145b..160866835c 100644 --- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts +++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts @@ -1,12 +1,9 @@ +import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js'; +import type { UmbContentTypeContainerStructureHelper } from '../../../structure/index.js'; import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { - UmbContentTypeContainerStructureHelper, - UmbContentTypeModel, - UmbPropertyTypeContainerModel, -} from '@umbraco-cms/backoffice/content-type'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import './content-type-design-editor-properties.element.js'; diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts index 65449ac606..932df6aad2 100644 --- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts +++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts @@ -1,4 +1,6 @@ import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace.context-token.js'; +import type { UmbContentTypeModel, UmbPropertyTypeModel } from '../../../types.js'; +import { UmbContentTypePropertyStructureHelper } from '../../../structure/index.js'; import { UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT } from './content-type-design-editor.context.js'; import type { UmbContentTypeDesignEditorPropertyElement } from './content-type-design-editor-property.element.js'; import { @@ -13,17 +15,15 @@ import { } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UmbContentTypePropertyStructureHelper } from '@umbraco-cms/backoffice/content-type'; -import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; - -import './content-type-design-editor-property.element.js'; import { UMB_CREATE_PROPERTY_TYPE_WORKSPACE_PATH_PATTERN, UMB_PROPERTY_TYPE_WORKSPACE_MODAL, } from '@umbraco-cms/backoffice/property-type'; +import './content-type-design-editor-property.element.js'; + const SORTER_CONFIG: UmbSorterConfig = { getUniqueOfElement: (element) => { return element.getAttribute('data-umb-property-id'); @@ -193,7 +193,7 @@ export class UmbContentTypeDesignEditorPropertiesElement extends UmbLitElement { } preset.sortOrder = sortOrderInt; } - return { data: { contentTypeUnique: this._ownerContentTypeUnique, preset: undefined } }; + return { data: { contentTypeUnique: this._ownerContentTypeUnique, preset: preset } }; }) .observeRouteBuilder((routeBuilder) => { this._newPropertyPath = diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts index 02374f4f8a..571b9a3c69 100644 --- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts +++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts @@ -1,3 +1,5 @@ +import type { UmbContentTypePropertyStructureHelper } from '../../../structure/index.js'; +import type { UmbContentTypeModel, UmbPropertyTypeModel, UmbPropertyTypeScaffoldModel } from '../../../types.js'; import { UmbPropertyTypeContext } from './content-type-design-editor-property.context.js'; import { css, html, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import { generateAlias } from '@umbraco-cms/backoffice/utils'; @@ -6,12 +8,6 @@ import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_EDIT_PROPERTY_TYPE_WORKSPACE_PATH_PATTERN } from '@umbraco-cms/backoffice/property-type'; -import type { - UmbContentTypeModel, - UmbContentTypePropertyStructureHelper, - UmbPropertyTypeModel, - UmbPropertyTypeScaffoldModel, -} from '@umbraco-cms/backoffice/content-type'; import type { UUIInputElement, UUIInputLockElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; /** diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts index 3b18576327..363f959008 100644 --- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts +++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts @@ -1,13 +1,13 @@ import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace.context-token.js'; +import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js'; +import { UmbContentTypeContainerStructureHelper } from '../../../structure/index.js'; import { UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT } from './content-type-design-editor.context.js'; import type { UmbContentTypeWorkspaceViewEditGroupElement } from './content-type-design-editor-group.element.js'; import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; -import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; -import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type'; import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter'; import './content-type-design-editor-properties.element.js'; diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts index 9105d91fd0..50d10eef50 100644 --- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts +++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts @@ -1,15 +1,14 @@ import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace.context-token.js'; +import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js'; +import { + UmbContentTypeContainerStructureHelper, + UmbContentTypeMoveRootGroupsIntoFirstTabHelper, +} from '../../../structure/index.js'; +import { UMB_COMPOSITION_PICKER_MODAL } from '../../../modals/index.js'; import type { UmbContentTypeDesignEditorTabElement } from './content-type-design-editor-tab.element.js'; import { UmbContentTypeDesignEditorContext } from './content-type-design-editor.context.js'; import { css, html, customElement, state, repeat, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { UUIInputElement, UUIInputEvent, UUITabElement } from '@umbraco-cms/backoffice/external/uui'; -import { - UMB_COMPOSITION_PICKER_MODAL, - UmbContentTypeContainerStructureHelper, - UmbContentTypeMoveRootGroupsIntoFirstTabHelper, - type UmbContentTypeModel, - type UmbPropertyTypeContainerModel, -} from '@umbraco-cms/backoffice/content-type'; import { encodeFolderName } from '@umbraco-cms/backoffice/router'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { CompositionTypeModel } from '@umbraco-cms/backoffice/external/backend-api'; diff --git a/src/packages/core/content-type/components/index.ts b/src/packages/core/content/components/index.ts similarity index 100% rename from src/packages/core/content-type/components/index.ts rename to src/packages/core/content/components/index.ts diff --git a/src/packages/core/content-type/components/property-type-based-property/index.ts b/src/packages/core/content/components/property-type-based-property/index.ts similarity index 100% rename from src/packages/core/content-type/components/property-type-based-property/index.ts rename to src/packages/core/content/components/property-type-based-property/index.ts diff --git a/src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts b/src/packages/core/content/components/property-type-based-property/property-type-based-property.element.ts similarity index 95% rename from src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts rename to src/packages/core/content/components/property-type-based-property/property-type-based-property.element.ts index 475188cd24..745b97c7c0 100644 --- a/src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts +++ b/src/packages/core/content/components/property-type-based-property/property-type-based-property.element.ts @@ -1,7 +1,6 @@ -import type { UmbPropertyEditorConfig } from '../../../property-editor/index.js'; -import type { UmbPropertyTypeModel } from '../../types.js'; +import { UmbContentPropertyContext } from '../../content-property.context.js'; +import type { UmbPropertyEditorConfig } from '@umbraco-cms/backoffice/property-editor'; import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit'; -import { UmbContentPropertyContext } from '@umbraco-cms/backoffice/content'; import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -9,6 +8,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type'; import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; import { UMB_UNSUPPORTED_EDITOR_SCHEMA_ALIASES } from '@umbraco-cms/backoffice/property'; +import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; @customElement('umb-property-type-based-property') export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { diff --git a/src/packages/core/content/constants.ts b/src/packages/core/content/constants.ts index cecd2baf55..05a27ced5d 100644 --- a/src/packages/core/content/constants.ts +++ b/src/packages/core/content/constants.ts @@ -1 +1 @@ -export const UMB_CONTENT_SECTION_ALIAS = 'Umb.Section.Content'; \ No newline at end of file +export const UMB_CONTENT_SECTION_ALIAS = 'Umb.Section.Content'; diff --git a/src/packages/core/content/controller/merge-content-variant-data.controller.ts b/src/packages/core/content/controller/merge-content-variant-data.controller.ts index aa50029af8..ae4e35af83 100644 --- a/src/packages/core/content/controller/merge-content-variant-data.controller.ts +++ b/src/packages/core/content/controller/merge-content-variant-data.controller.ts @@ -1,5 +1,5 @@ +import type { UmbContentLikeDetailModel, UmbPotentialContentValueModel } from '../types.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import type { UmbContentLikeDetailModel, UmbPotentialContentValueModel } from '@umbraco-cms/backoffice/content'; import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbVariantId, type UmbVariantDataModel } from '@umbraco-cms/backoffice/variant'; diff --git a/src/packages/core/content/index.ts b/src/packages/core/content/index.ts index 1fe642380a..338c9631c1 100644 --- a/src/packages/core/content/index.ts +++ b/src/packages/core/content/index.ts @@ -1,9 +1,12 @@ export { UMB_CONTENT_PROPERTY_CONTEXT } from './content-property.context-token.js'; export { UmbContentPropertyContext } from './content-property.context.js'; + export * from './collection/index.js'; +export * from './components/index.js'; export * from './constants.js'; export * from './controller/merge-content-variant-data.controller.js'; export * from './manager/index.js'; export * from './property-dataset-context/index.js'; +export * from './variant-picker/index.js'; export * from './workspace/index.js'; export type * from './types.js'; diff --git a/src/packages/core/content/manager/content-data-manager.ts b/src/packages/core/content/manager/content-data-manager.ts index 3c6601037f..d8d375eb4c 100644 --- a/src/packages/core/content/manager/content-data-manager.ts +++ b/src/packages/core/content/manager/content-data-manager.ts @@ -1,5 +1,5 @@ +import type { UmbContentDetailModel } from '../types.js'; import { UmbElementWorkspaceDataManager } from './element-data-manager.js'; -import type { UmbContentDetailModel } from '@umbraco-cms/backoffice/content'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { appendToFrozenArray, jsonStringComparison } from '@umbraco-cms/backoffice/observable-api'; import { UmbVariantId, type UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant'; @@ -14,11 +14,20 @@ export class UmbContentWorkspaceDataManager< //#repository; #variantScaffold?: ModelVariantType; - constructor(host: UmbControllerHost, variantScaffold: ModelVariantType) { + constructor(host: UmbControllerHost, variantScaffold?: ModelVariantType) { super(host); this.#variantScaffold = variantScaffold; } + /** + * Sets the variant scaffold data + * @param {ModelVariantType} variantScaffold The variant scaffold data + * @memberof UmbContentWorkspaceDataManager + */ + setVariantScaffold(variantScaffold: ModelVariantType) { + this.#variantScaffold = variantScaffold; + } + ensureVariantData(variantId: UmbVariantId) { this.updateVariantData(variantId); } diff --git a/src/packages/core/content/manager/element-data-manager.ts b/src/packages/core/content/manager/element-data-manager.ts index 7dd3ddc91e..dfac318c5d 100644 --- a/src/packages/core/content/manager/element-data-manager.ts +++ b/src/packages/core/content/manager/element-data-manager.ts @@ -1,5 +1,5 @@ import { UmbMergeContentVariantDataController } from '../controller/merge-content-variant-data.controller.js'; -import type { UmbElementDetailModel } from '@umbraco-cms/backoffice/content'; +import type { UmbElementDetailModel } from '../types.js'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UmbEntityWorkspaceDataManager, type UmbWorkspaceDataManager } from '@umbraco-cms/backoffice/workspace'; diff --git a/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts b/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts index 0490888eb7..bf9657c99a 100644 --- a/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts +++ b/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts @@ -215,7 +215,7 @@ export abstract class UmbElementPropertyDatasetContext< override destroy() { super.destroy(); - this.#propertyVariantIdMap.destroy(); + this.#propertyVariantIdMap?.destroy(); (this.#propertyVariantIdMap as unknown) = undefined; } } diff --git a/src/packages/core/content/types.ts b/src/packages/core/content/types.ts index a1e7c8cd01..169c1d43b0 100644 --- a/src/packages/core/content/types.ts +++ b/src/packages/core/content/types.ts @@ -19,10 +19,11 @@ export interface UmbPotentialContentValueModel extends UmbP segment?: string | null; } -export interface UmbContentDetailModel extends UmbElementDetailModel { +export interface UmbContentDetailModel + extends UmbElementDetailModel { unique: string; entityType: string; - variants: Array; + variants: Array; } export interface UmbContentLikeDetailModel diff --git a/src/packages/core/content/variant-picker/index.ts b/src/packages/core/content/variant-picker/index.ts new file mode 100644 index 0000000000..d4702960d5 --- /dev/null +++ b/src/packages/core/content/variant-picker/index.ts @@ -0,0 +1 @@ +export * from './types.js'; diff --git a/src/packages/core/content/variant-picker/types.ts b/src/packages/core/content/variant-picker/types.ts new file mode 100644 index 0000000000..42a4467c92 --- /dev/null +++ b/src/packages/core/content/variant-picker/types.ts @@ -0,0 +1,8 @@ +export interface UmbContentVariantPickerData { + options: Array; + pickableFilter?: (variantOption: VariantOptionModelType) => boolean; +} + +export interface UmbContentVariantPickerValue { + selection: Array; +} diff --git a/src/packages/core/content/workspace/content-detail-workspace-base.ts b/src/packages/core/content/workspace/content-detail-workspace-base.ts new file mode 100644 index 0000000000..3ae802cc25 --- /dev/null +++ b/src/packages/core/content/workspace/content-detail-workspace-base.ts @@ -0,0 +1,621 @@ +import type { UmbContentDetailModel, UmbElementValueModel } from '../types.js'; +import { UmbContentWorkspaceDataManager } from '../manager/index.js'; +import { UmbMergeContentVariantDataController } from '../controller/merge-content-variant-data.controller.js'; +import type { UmbContentVariantPickerData, UmbContentVariantPickerValue } from '../variant-picker/index.js'; +import type { UmbContentPropertyDatasetContext } from '../property-dataset-context/index.js'; +import type { UmbContentWorkspaceContext } from './content-workspace-context.interface.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbDetailRepository, UmbDetailRepositoryConstructor } from '@umbraco-cms/backoffice/repository'; +import { + UmbEntityDetailWorkspaceContextBase, + UmbWorkspaceSplitViewManager, + type UmbEntityDetailWorkspaceContextArgs, + type UmbEntityDetailWorkspaceContextCreateArgs, +} from '@umbraco-cms/backoffice/workspace'; +import { + UmbContentTypeStructureManager, + type UmbContentTypeModel, + type UmbPropertyTypeModel, +} from '@umbraco-cms/backoffice/content-type'; +import { + UMB_INVARIANT_CULTURE, + UmbVariantId, + type UmbEntityVariantModel, + type UmbEntityVariantOptionModel, +} from '@umbraco-cms/backoffice/variant'; +import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; +import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; +import { appendToFrozenArray, mergeObservables, UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; +import { + UMB_VALIDATION_CONTEXT, + UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + UmbDataPathVariantQuery, + UmbValidationContext, + UmbVariantsValidationPathTranslator, + UmbVariantValuesValidationPathTranslator, +} from '@umbraco-cms/backoffice/validation'; +import type { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import { + UmbRequestReloadChildrenOfEntityEvent, + UmbRequestReloadStructureForEntityEvent, +} from '@umbraco-cms/backoffice/entity-action'; + +export interface UmbContentDetailWorkspaceContextArgs< + DetailModelType extends UmbContentDetailModel, + ContentTypeDetailModelType extends UmbContentTypeModel = UmbContentTypeModel, + VariantModelType extends UmbEntityVariantModel = DetailModelType extends { variants: UmbEntityVariantModel[] } + ? DetailModelType['variants'][0] + : never, + VariantOptionModelType extends UmbEntityVariantOptionModel = UmbEntityVariantOptionModel, +> extends UmbEntityDetailWorkspaceContextArgs { + contentTypeDetailRepository: UmbDetailRepositoryConstructor; + contentVariantScaffold: VariantModelType; + saveModalToken?: UmbModalToken, UmbContentVariantPickerValue>; +} + +export abstract class UmbContentDetailWorkspaceContextBase< + DetailModelType extends UmbContentDetailModel, + DetailRepositoryType extends UmbDetailRepository = UmbDetailRepository, + ContentTypeDetailModelType extends UmbContentTypeModel = UmbContentTypeModel, + VariantModelType extends UmbEntityVariantModel = DetailModelType extends { variants: UmbEntityVariantModel[] } + ? DetailModelType['variants'][0] + : never, + VariantOptionModelType extends UmbEntityVariantOptionModel = UmbEntityVariantOptionModel, + CreateArgsType extends + UmbEntityDetailWorkspaceContextCreateArgs = UmbEntityDetailWorkspaceContextCreateArgs, + > + extends UmbEntityDetailWorkspaceContextBase + implements UmbContentWorkspaceContext +{ + public readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const; + + public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); + + /* Content Data */ + protected override readonly _data = new UmbContentWorkspaceDataManager(this); + public override readonly entityType = this._data.createObservablePartOfCurrent((data) => data?.entityType); + public override readonly unique = this._data.createObservablePartOfCurrent((data) => data?.unique); + public readonly values = this._data.createObservablePartOfCurrent((data) => data?.values); + public readonly variants = this._data.createObservablePartOfCurrent((data) => data?.variants ?? []); + + /* Content Type (Structure) Data */ + public readonly structure; + public readonly variesByCulture; + public readonly variesBySegment; + public readonly varies; + + abstract readonly contentTypeUnique: Observable; + + /* Data Type */ + readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); + /** + * Data Type Schema Map is used for lookup, this should make coder simpler and give better performance. [NL] + */ + #dataTypeSchemaAliasMap = new Map(); + + #varies?: boolean; + #variesByCulture?: boolean; + #variesBySegment?: boolean; + + /* Split View */ + readonly splitView = new UmbWorkspaceSplitViewManager(); + + /* Variant Options */ + // TODO: Optimize this so it uses either a App Language Context? [NL] + #languageRepository = new UmbLanguageCollectionRepository(this); + #languages = new UmbArrayState([], (x) => x.unique); + /** + * @private + * @description - Should not be used by external code. + */ + public readonly languages = this.#languages.asObservable(); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // TODO: fix type error + public readonly variantOptions; + + #saveModalToken?: UmbModalToken, UmbContentVariantPickerValue>; + + constructor( + host: UmbControllerHost, + args: UmbContentDetailWorkspaceContextArgs< + DetailModelType, + ContentTypeDetailModelType, + VariantModelType, + VariantOptionModelType + >, + ) { + super(host, args); + + this._data.setVariantScaffold(args.contentVariantScaffold); + this.#saveModalToken = args.saveModalToken; + + const contentTypeDetailRepository = new args.contentTypeDetailRepository(this); + this.structure = new UmbContentTypeStructureManager(this, contentTypeDetailRepository); + this.variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); + this.variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); + this.varies = this.structure.ownerContentTypeObservablePart((x) => + x ? x.variesByCulture || x.variesBySegment : undefined, + ); + + this.variantOptions = mergeObservables( + [this.varies, this.variants, this.languages], + ([varies, variants, languages]) => { + // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL] + if (varies === true) { + return languages.map((language) => { + return { + variant: variants.find((x) => x.culture === language.unique), + language, + // TODO: When including segments, this object should be updated to include a object for the segment. [NL] + // TODO: When including segments, the unique should be updated to include the segment as well. [NL] + unique: language.unique, // This must be a variantId string! + culture: language.unique, + segment: null, + } as VariantOptionModelType; + }); + } else if (varies === false) { + return [ + { + variant: variants.find((x) => x.culture === null), + language: languages.find((x) => x.isDefault), + culture: null, + segment: null, + unique: UMB_INVARIANT_CULTURE, // This must be a variantId string! + } as VariantOptionModelType, + ]; + } + return [] as Array; + }, + ); + + this.addValidationContext(new UmbValidationContext(this)); + new UmbVariantValuesValidationPathTranslator(this); + new UmbVariantsValidationPathTranslator(this); + + this.observe( + this.varies, + (varies) => { + this._data.setVaries(varies); + this.#varies = varies; + }, + null, + ); + this.observe( + this.variesByCulture, + (varies) => { + this._data.setVariesByCulture(varies); + this.#variesByCulture = varies; + }, + null, + ); + this.observe( + this.variesBySegment, + (varies) => { + this._data.setVariesBySegment(varies); + this.#variesBySegment = varies; + }, + null, + ); + this.observe( + this.structure.contentTypeDataTypeUniques, + (dataTypeUniques: Array) => { + this.#dataTypeItemManager.setUniques(dataTypeUniques); + }, + null, + ); + this.observe( + this.#dataTypeItemManager.items, + (dataTypes) => { + // Make a map of the data type unique and editorAlias + this.#dataTypeSchemaAliasMap = new Map( + dataTypes.map((dataType) => { + return [dataType.unique, dataType.propertyEditorSchemaAlias]; + }), + ); + }, + null, + ); + + this.loadLanguages(); + } + + public async loadLanguages() { + // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] + const { data } = await this.#languageRepository.requestCollection({}); + this.#languages.setValue(data?.items ?? []); + } + + /** + * Get the name of a variant + * @param {UmbVariantId } [variantId] - The variant id + * @returns { string | undefined} - The name of the variant + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getName(variantId?: UmbVariantId): string | undefined { + const variants = this._data.getCurrent()?.variants; + if (!variants) return; + if (variantId) { + return variants.find((x) => variantId.compare(x))?.name; + } else { + return variants[0]?.name; + } + } + + /** + * Set the name of a variant + * @param {string} name - The name of the variant + * @param {UmbVariantId} [variantId] - The variant id + * @memberof UmbContentDetailWorkspaceContextBase + */ + public setName(name: string, variantId?: UmbVariantId): void { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // TODO: fix type error + this._data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); + } + + /** + * Get an observable for the name of a variant + * @param {UmbVariantId} [variantId] - The variant id + * @returns {Observable} - The name of the variant + * @memberof UmbContentDetailWorkspaceContextBase + */ + public name(variantId?: UmbVariantId): Observable { + return this._data.createObservablePartOfCurrent( + (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '', + ); + } + + /* Variants */ + + /** + * Get whether the content varies by culture + * @returns { boolean | undefined } - If the content varies by culture + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getVariesByCulture(): boolean | undefined { + return this.#variesByCulture; + } + + /** + * Get whether the content varies by segment + * @returns {boolean | undefined} - If the content varies by segment + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getVariesBySegment(): boolean | undefined { + return this.#variesBySegment; + } + + /** + * Get whether the content varies + * @returns { boolean | undefined } - If the content varies + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getVaries(): boolean | undefined { + return this.#varies; + } + + /** + * Get the variant by the given variantId + * @param {UmbVariantId} variantId - The variant id + * @returns { Observable } - The variant or undefined if not found + * @memberof UmbContentDetailWorkspaceContextBase + */ + public variantById(variantId: UmbVariantId): Observable { + return this._data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x))); + } + + /** + * Get the variant by the given variantId + * @param {UmbVariantId} variantId - The variant id + * @returns { VariantModelType | undefined } - The variant or undefined if not found + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getVariant(variantId: UmbVariantId): VariantModelType | undefined { + return this._data.getCurrent()?.variants?.find((x) => variantId.compare(x)); + } + + /** + * Observe the property type + * @param {string} propertyId - The id of the property + * @returns {Promise>} - An observable for the property type + * @memberof UmbContentDetailWorkspaceContextBase + */ + public async propertyStructureById(propertyId: string): Promise> { + return this.structure.propertyStructureById(propertyId); + } + + /* Values */ + + /** + * Get the values of the content + * @returns {Array | undefined} - The values of the content + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getValues(): Array | undefined { + return this._data.getCurrent()?.values; + } + + /** + * @function propertyValueByAlias + * @param {string} propertyAlias - The alias of the property + * @param {UmbVariantId} variantId - The variant + * @returns {Promise | undefined>} - An observable for the value of the property + * @description Get an Observable for the value of this property. + */ + public async propertyValueByAlias( + propertyAlias: string, + variantId?: UmbVariantId, + ): Promise | undefined> { + return this._data.createObservablePartOfCurrent( + (data) => + data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true)) + ?.value as PropertyValueType, + ); + } + + /** + * Get the current value of the property with the given alias and variantId. + * @param {string} alias - The alias of the property + * @param {UmbVariantId | undefined} variantId - The variant id of the property + * @returns {ReturnType | undefined} The value or undefined if not set or found. + */ + public getPropertyValue(alias: string, variantId?: UmbVariantId) { + const currentData = this._data.getCurrent(); + if (currentData) { + const newDataSet = currentData.values?.find( + (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), + ); + return newDataSet?.value as ReturnType; + } + return undefined; + } + + /** + * Set the value of the property with the given alias and variantId. + * @template ValueType + * @param {string} alias - The alias of the property + * @param {ValueType} value - The value to set + * @param {UmbVariantId} [variantId] - The variant id of the property + * @memberof UmbContentDetailWorkspaceContextBase + */ + public async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { + this.initiatePropertyValueChange(); + variantId ??= UmbVariantId.CreateInvariant(); + const property = await this.structure.getPropertyStructureByAlias(alias); + + if (!property) { + throw new Error(`Property alias "${alias}" not found.`); + } + + const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); + if (!editorAlias) { + throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); + } + + // Notice the order of the properties is important for our JSON String Compare function. [NL] + const entry = { editorAlias, alias, ...variantId.toObject(), value } as UmbElementValueModel; + + const currentData = this.getData(); + if (currentData) { + const values = appendToFrozenArray( + currentData.values ?? [], + entry, + (x) => x.alias === alias && variantId!.compare(x), + ); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // TODO: fix type error + this._data.updateCurrent({ values }); + + // TODO: Ideally we should move this type of logic to the act of saving [NL] + this._data.ensureVariantData(variantId); + } + this.finishPropertyValueChange(); + } + + public initiatePropertyValueChange() { + this._data.initiatePropertyValueChange(); + } + + public finishPropertyValueChange = () => { + this._data.finishPropertyValueChange(); + }; + + protected async _determineVariantOptions() { + const options = await firstValueFrom(this.variantOptions); + + const activeVariants = this.splitView.getActiveVariants(); + const activeVariantIds = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant)); + const changedVariantIds = this._data.getChangedVariants(); + const selectedVariantIds = activeVariantIds.concat(changedVariantIds); + + // Selected can contain entries that are not part of the options, therefor the modal filters selection based on options. + const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture); + let selected = selectedVariantIds.map((x) => x.toString()).filter((v, i, a) => a.indexOf(v) === i); + selected = selected.filter((x) => readOnlyCultures.includes(x) === false); + + return { + options, + selected, + }; + } + + protected _readOnlyLanguageVariantsFilter = (option: VariantOptionModelType) => { + const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture); + return readOnlyCultures.includes(option.culture) === false; + }; + + /* validation */ + protected async _runMandatoryValidationForSaveData(saveData: DetailModelType) { + // Check that the data is valid before we save it. + // Check variants have a name: + const variantsWithoutAName = saveData.variants.filter((x) => !x.name); + if (variantsWithoutAName.length > 0) { + const validationContext = await this.getContext(UMB_VALIDATION_CONTEXT); + variantsWithoutAName.forEach((variant) => { + validationContext.messages.addMessage( + 'client', + `$.variants[${UmbDataPathVariantQuery(variant)}].name`, + UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + ); + }); + throw new Error('All variants must have a name'); + } + } + + /** + * Request a submit of the workspace, in the case of Content Workspaces the validation does not need to be valid for this to be submitted. + * @returns {Promise} a promise which resolves once it has been completed. + */ + public override requestSubmit() { + return this.#handleSubmit(); + } + + public override submit() { + return this.#handleSubmit(); + } + + // Because we do not make validation prevent submission this also submits the workspace. [NL] + public override invalidSubmit() { + return this.#handleSubmit(); + } + + async #handleSubmit() { + const data = this.getData(); + if (!data) { + throw new Error('Data is missing'); + } + + const { options, selected } = await this._determineVariantOptions(); + + let variantIds: Array = []; + + // If there is only one variant, we don't need to open the modal. + if (options.length === 0) { + throw new Error('No variants are available'); + } else if (options.length === 1) { + // If only one option we will skip ahead and save the content with the only variant available: + variantIds.push(UmbVariantId.Create(options[0])); + } else if (this.#saveModalToken) { + // If there are multiple variants, we will open the modal to let the user pick which variants to save. + const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const result = await modalManagerContext + .open(this, this.#saveModalToken, { + data: { + options, + pickableFilter: this._readOnlyLanguageVariantsFilter, + }, + value: { selection: selected }, + }) + .onSubmit() + .catch(() => undefined); + + if (!result?.selection.length) return; + + variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; + } else { + throw new Error('No variant picker modal token is set. There are multiple variants to save. Cannot proceed.'); + } + + const saveData = await this._data.constructData(variantIds); + await this._runMandatoryValidationForSaveData(saveData); + await this._performCreateOrUpdate(variantIds, saveData); + } + + protected async _performCreateOrUpdate(variantIds: Array, saveData: DetailModelType) { + if (this.getIsNew()) { + await this.#create(variantIds, saveData); + } else { + await this.#update(variantIds, saveData); + } + } + + async #create(variantIds: Array, saveData: DetailModelType) { + if (!this._detailRepository) throw new Error('Detail repository is not set'); + + const parent = this.getParent(); + if (!parent) throw new Error('Parent is not set'); + + const { data, error } = await this._detailRepository.create(saveData, parent.unique); + if (!data || error) { + throw new Error('Error creating content'); + } + + this._data.setPersisted(data); + + const currentData = this._data.getCurrent(); + + const variantIdsIncludingInvariant = [...variantIds, UmbVariantId.CreateInvariant()]; + + // Retrieve a data set which only contains updates from the selected variants + invariant. [NL] + const newCurrentData = await new UmbMergeContentVariantDataController(this).process( + currentData, + data, + variantIds, + variantIdsIncludingInvariant, + ); + + this._data.setCurrent(newCurrentData); + + const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbRequestReloadChildrenOfEntityEvent({ + entityType: parent.entityType, + unique: parent.unique, + }); + eventContext.dispatchEvent(event); + this.setIsNew(false); + } + + async #update(variantIds: Array, saveData: DetailModelType) { + if (!this._detailRepository) throw new Error('Detail repository is not set'); + + const { data, error } = await this._detailRepository.save(saveData); + if (!data || error) { + throw new Error('Error saving content'); + } + + this._data.setPersisted(data); + // TODO: Only update the variants that was chosen to be saved: + const currentData = this._data.getCurrent(); + + const variantIdsIncludingInvariant = [...variantIds, UmbVariantId.CreateInvariant()]; + + const newCurrentData = await new UmbMergeContentVariantDataController(this).process( + currentData, + data, + variantIds, + variantIdsIncludingInvariant, + ); + this._data.setCurrent(newCurrentData); + + const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbRequestReloadStructureForEntityEvent({ + entityType: this.getEntityType(), + unique: this.getUnique()!, + }); + + eventContext.dispatchEvent(event); + } + + abstract getContentTypeUnique(): string | undefined; + + abstract createPropertyDatasetContext( + host: UmbControllerHost, + variantId: UmbVariantId, + ): UmbContentPropertyDatasetContext; + + public override destroy(): void { + this.structure.destroy(); + this.#languageRepository.destroy(); + super.destroy(); + } +} diff --git a/src/packages/core/content/workspace/content-workspace-context.interface.ts b/src/packages/core/content/workspace/content-workspace-context.interface.ts index ecaeca41a7..ba1b17c59c 100644 --- a/src/packages/core/content/workspace/content-workspace-context.interface.ts +++ b/src/packages/core/content/workspace/content-workspace-context.interface.ts @@ -1,4 +1,5 @@ -import type { UmbContentDetailModel, UmbElementPropertyDataOwner } from '@umbraco-cms/backoffice/content'; +import type { UmbContentDetailModel } from '../types.js'; +import type { UmbElementPropertyDataOwner } from '../property-dataset-context/index.js'; import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbVariantId, UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant'; diff --git a/src/packages/core/content/workspace/index.ts b/src/packages/core/content/workspace/index.ts index 4c72bcced7..2d6e425ec3 100644 --- a/src/packages/core/content/workspace/index.ts +++ b/src/packages/core/content/workspace/index.ts @@ -1,3 +1,4 @@ -export type * from './content-workspace-context.interface.js'; +export * from './content-detail-workspace-base.js'; export * from './content-workspace.context-token.js'; export * from './views/edit/index.js'; +export type * from './content-workspace-context.interface.js'; diff --git a/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts b/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts new file mode 100644 index 0000000000..453664f142 --- /dev/null +++ b/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts @@ -0,0 +1,41 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import { html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +const elementName = 'umb-entity-actions-table-column-view'; +@customElement(elementName) +export class UmbEntityActionsTableColumnViewElement extends UmbLitElement { + @property({ attribute: false }) + value?: UmbEntityModel; + + @state() + _isOpen = false; + + #onActionExecuted() { + this._isOpen = false; + } + + #onClick(event: Event) { + event.stopPropagation(); + } + + override render() { + if (!this.value) return nothing; + + return html` + + + + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbEntityActionsTableColumnViewElement; + } +} diff --git a/src/packages/core/entity-action/global-components/index.ts b/src/packages/core/entity-action/global-components/index.ts new file mode 100644 index 0000000000..841ca85af1 --- /dev/null +++ b/src/packages/core/entity-action/global-components/index.ts @@ -0,0 +1 @@ +import './entity-actions-table-column-view/entity-actions-table-column-view.element.js'; diff --git a/src/packages/core/entity-action/index.ts b/src/packages/core/entity-action/index.ts index 60881da438..bf8c1a0d9b 100644 --- a/src/packages/core/entity-action/index.ts +++ b/src/packages/core/entity-action/index.ts @@ -1,3 +1,5 @@ +import './global-components/index.js'; + export * from './common/index.js'; export * from './default/index.js'; export * from './entity-action-base.js'; diff --git a/src/packages/core/localization/manifests.ts b/src/packages/core/localization/manifests.ts index 576c677f65..e8741a538a 100644 --- a/src/packages/core/localization/manifests.ts +++ b/src/packages/core/localization/manifests.ts @@ -61,6 +61,16 @@ export const manifests: Array = [ }, js: () => import('../../../assets/lang/de-de.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.De-CH', + weight: -100, + name: 'Deutsch (Schweiz)', + meta: { + culture: 'de-ch', + }, + js: () => import('../../../assets/lang/de-ch.js'), + }, { type: 'localization', alias: 'Umb.Localization.En-GB', @@ -101,6 +111,16 @@ export const manifests: Array = [ }, js: () => import('../../../assets/lang/fr-fr.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.Fr-CH', + weight: -100, + name: 'Français (Suisse)', + meta: { + culture: 'fr-ch', + }, + js: () => import('../../../assets/lang/fr-ch.js'), + }, { type: 'localization', alias: 'Umb.Localization.He-IL', @@ -131,6 +151,16 @@ export const manifests: Array = [ }, js: () => import('../../../assets/lang/it-it.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.It-CH', + weight: -100, + name: 'Italiano (Svizerra)', + meta: { + culture: 'it-ch', + }, + js: () => import('../../../assets/lang/it-ch.js'), + }, { type: 'localization', alias: 'Umb.Localization.Ja-JP', diff --git a/src/packages/core/modal/component/modal.element.ts b/src/packages/core/modal/component/modal.element.ts index 2a52a72b50..a192488d3a 100644 --- a/src/packages/core/modal/component/modal.element.ts +++ b/src/packages/core/modal/component/modal.element.ts @@ -14,10 +14,14 @@ import { type UUIModalDialogElement, type UUIModalSidebarElement, } from '@umbraco-cms/backoffice/external/uui'; -import type { UmbRouterSlotElement } from '@umbraco-cms/backoffice/router'; +import { UMB_ROUTE_CONTEXT, type UmbRouterSlotElement } from '@umbraco-cms/backoffice/router'; import { createExtensionElement, loadManifestElement } from '@umbraco-cms/backoffice/extension-api'; import type { UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api'; -import { UMB_CONTENT_REQUEST_EVENT_TYPE, UmbContextProvider } from '@umbraco-cms/backoffice/context-api'; +import { + UMB_CONTEXT_REQUEST_EVENT_TYPE, + UmbContextBoundary, + UmbContextProvider, +} from '@umbraco-cms/backoffice/context-api'; @customElement('umb-modal') export class UmbModalElement extends UmbLitElement { @@ -33,7 +37,6 @@ export class UmbModalElement extends UmbLitElement { this.destroy(); return; } - } public element?: UUIModalDialogElement | UUIModalSidebarElement | UUIModalElement; @@ -41,7 +44,7 @@ export class UmbModalElement extends UmbLitElement { #innerElement = new UmbBasicState(undefined); #modalExtensionObserver?: UmbObserverController; - #modalRouterElement: UmbRouterSlotElement = document.createElement('umb-router-slot'); + #modalRouterElement?: HTMLDivElement | UmbRouterSlotElement; #onClose = () => { this.element?.removeEventListener(UUIModalCloseEvent, this.#onClose); @@ -59,7 +62,7 @@ export class UmbModalElement extends UmbLitElement { // The following code is the context api proxy. // It re-dispatches the context api request event to the origin target of this modal, in other words the element that initiated the modal. [NL] - this.element.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => { + this.element.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => { if (!this.#modalContext) return; // Note for this hack (The if-sentence): [NL] // We do not currently have a good enough control to ensure that the proxy is last, meaning if another context is provided at this element, it might respond after the proxy event has been dispatched. @@ -85,6 +88,7 @@ export class UmbModalElement extends UmbLitElement { * */ if (this.#modalContext.router) { + this.#modalRouterElement = document.createElement('umb-router-slot'); this.#modalRouterElement.routes = [ { path: '', @@ -92,9 +96,16 @@ export class UmbModalElement extends UmbLitElement { }, ]; this.#modalRouterElement.parent = this.#modalContext.router; + } else { + this.#modalRouterElement = document.createElement('div'); + // Notice inline styling here is used cause the element is not appended into this elements shadowDom but outside and there by gets into the element via a slot. + this.#modalRouterElement.style.position = 'relative'; + this.#modalRouterElement.style.height = '100%'; + new UmbContextBoundary(this.#modalRouterElement, UMB_ROUTE_CONTEXT).hostConnected(); } this.element.appendChild(this.#modalRouterElement); + this.#observeModal(this.#modalContext.alias.toString()); const provider = new UmbContextProvider(this.element, UMB_MODAL_CONTEXT, this.#modalContext); @@ -102,10 +113,8 @@ export class UmbModalElement extends UmbLitElement { } async #createContainerElement() { - - if(this.#modalContext!.type == 'custom' && this.#modalContext?.element) - { - var customWrapperElementCtor = await loadManifestElement(this.#modalContext.element); + if (this.#modalContext!.type == 'custom' && this.#modalContext?.element) { + const customWrapperElementCtor = await loadManifestElement(this.#modalContext.element); return new customWrapperElementCtor!(); } @@ -158,14 +167,14 @@ export class UmbModalElement extends UmbLitElement { } #appendInnerElement(element: HTMLElement) { - this.#modalRouterElement.appendChild(element); + this.#modalRouterElement!.appendChild(element); this.#innerElement.setValue(element); } #removeInnerElement() { const innerElement = this.#innerElement.getValue(); if (innerElement) { - this.#modalRouterElement.removeChild(innerElement); + this.#modalRouterElement!.removeChild(innerElement); this.#innerElement.setValue(undefined); } } diff --git a/src/packages/core/property-type/workspace/property-type-workspace.context.ts b/src/packages/core/property-type/workspace/property-type-workspace.context.ts index b9bcfe9751..20afdc3bb3 100644 --- a/src/packages/core/property-type/workspace/property-type-workspace.context.ts +++ b/src/packages/core/property-type/workspace/property-type-workspace.context.ts @@ -72,7 +72,7 @@ export class UmbPropertyTypeWorkspaceContext { + new (host: UmbControllerHost): UmbDetailRepository; +} + export interface UmbDetailRepository extends UmbReadDetailRepository, UmbApi { createScaffold(preset?: Partial): Promise>; create(data: DetailModelType, parentUnique: string | null): Promise>; diff --git a/src/packages/core/repository/detail/index.ts b/src/packages/core/repository/detail/index.ts index f190db5763..3e13f2e154 100644 --- a/src/packages/core/repository/detail/index.ts +++ b/src/packages/core/repository/detail/index.ts @@ -9,3 +9,5 @@ export type { UmbReadDetailRepository } from './read/read-detail-repository.inte export type { UmbDetailDataSource, UmbDetailDataSourceConstructor } from './detail-data-source.interface.js'; export { UmbDetailRepositoryBase } from './detail-repository-base.js'; export type { UmbDetailRepository } from './detail-repository.interface.js'; + +export * from './detail-repository.interface.js'; diff --git a/src/packages/core/router/modal-registration/modal-route-registration.controller.ts b/src/packages/core/router/modal-registration/modal-route-registration.controller.ts index d487de0fff..1ee307a27a 100644 --- a/src/packages/core/router/modal-registration/modal-route-registration.controller.ts +++ b/src/packages/core/router/modal-registration/modal-route-registration.controller.ts @@ -259,9 +259,9 @@ export class UmbModalRouteRegistrationController< } public open(params: { [key: string]: string | number }, prepend?: string) { - if (this.active) return; + if (this.active || !this.#routeBuilder) return; - window.history.pushState({}, '', this.#routeBuilder?.(params) + (prepend ? `${prepend}` : '')); + window.history.pushState({}, '', this.#routeBuilder(params) + (prepend ? `${prepend}` : '')); } /** @@ -277,6 +277,7 @@ export class UmbModalRouteRegistrationController< return this; } public _internal_setRouteBuilder(urlBuilder: UmbModalRouteBuilder) { + if (!this.#routeContext) return; this.#routeBuilder = urlBuilder; this.#urlBuilderCallback?.(urlBuilder); } diff --git a/src/packages/core/router/route.context.ts b/src/packages/core/router/route.context.ts index 7fda170903..f8f15291d4 100644 --- a/src/packages/core/router/route.context.ts +++ b/src/packages/core/router/route.context.ts @@ -123,7 +123,7 @@ export class UmbRouteContext extends UmbContextBase { if (this.#activeModalPath) { // If if there is a modal using the old path. const activeModal = this.#modalRegistrations.find((registration) => { - return registration.generateModalPath() === this.#activeModalPath; + return '/' + registration.generateModalPath() === this.#activeModalPath; }); if (activeModal) { this.#modalContext?.close(activeModal.key); diff --git a/src/packages/core/router/router-slot.element.ts b/src/packages/core/router/router-slot.element.ts index 6b45549d73..a78f0e2ac6 100644 --- a/src/packages/core/router/router-slot.element.ts +++ b/src/packages/core/router/router-slot.element.ts @@ -94,9 +94,7 @@ export class UmbRouterSlotElement extends UmbLitElement { protected override firstUpdated(_changedProperties: PropertyValueMap | Map): void { super.firstUpdated(_changedProperties); - this._routerPath = this._constructAbsoluteRouterPath(); - this.#routeContext._internal_routerGotBasePath(this._routerPath); - this.dispatchEvent(new UmbRouterSlotInitEvent()); + this._updateRouterPath(); } protected _updateRouterPath() { @@ -124,7 +122,7 @@ export class UmbRouterSlotElement extends UmbLitElement { this.dispatchEvent(new UmbRouterSlotChangeEvent()); } } else if (event.detail.slot === this.#modalRouter) { - const newActiveModalLocalPath = this.#modalRouter.match?.fragments.consumed ?? ''; + const newActiveModalLocalPath = this.#modalRouter.match?.route.path ?? ''; this.#routeContext._internal_modalRouterChanged(newActiveModalLocalPath); } }; diff --git a/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts b/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts index e2951a2b78..5410e8caa0 100644 --- a/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts +++ b/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts @@ -12,7 +12,6 @@ const observeSymbol = Symbol(); * This controller will add a custom error to the form control if the validation context has any messages for the specified data path. */ export class UmbBindServerValidationToFormControl extends UmbControllerBase { - #context?: typeof UMB_VALIDATION_CONTEXT.TYPE; #control: UmbFormControlMixinInterface; @@ -41,7 +40,7 @@ export class UmbBindServerValidationToFormControl extends UmbControllerBase { } constructor(host: UmbControllerHost, formControl: UmbFormControlMixinInterface, dataPath: string) { - super(host,'umbFormControlValidation_'+simpleHashCode(dataPath)); + super(host, 'umbFormControlValidation_' + simpleHashCode(dataPath)); this.#control = formControl; this.consumeContext(UMB_VALIDATION_CONTEXT, (context) => { this.#context = context; diff --git a/src/packages/core/validation/controllers/validation.controller.ts b/src/packages/core/validation/controllers/validation.controller.ts index feaf76897a..e535dc933d 100644 --- a/src/packages/core/validation/controllers/validation.controller.ts +++ b/src/packages/core/validation/controllers/validation.controller.ts @@ -79,13 +79,16 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal this.messages.removeTranslator(translator); } + #currentProvideHost?: UmbClassInterface; /** * Provide this validation context to a specific controller host. * This can be used to Host a validation context in a Workspace, but provide it on a certain scope, like a specific Workspace View. * @param controllerHost {UmbClassInterface} */ provideAt(controllerHost: UmbClassInterface): void { + if (this.#currentProvideHost === controllerHost) return; this.#providerCtrl?.destroy(); + this.#currentProvideHost = controllerHost; this.#providerCtrl = controllerHost.provideContext(UMB_VALIDATION_CONTEXT, this); } diff --git a/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts b/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts index 121310af55..4e7200a340 100644 --- a/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts +++ b/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts @@ -1,12 +1,17 @@ import { UmbValidationPathTranslatorBase } from './validation-path-translator-base.controller.js'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export abstract class UmbAbstractArrayValidationPathTranslator extends UmbValidationPathTranslatorBase { #initialPathToMatch: string; #queryMethod: (data: unknown) => string; - constructor(host: UmbControllerHost, initialPathToMatch: string, queryMethod: (data: any) => string) { - super(host); + constructor( + host: UmbControllerHost, + initialPathToMatch: string, + queryMethod: (data: any) => string, + ctrlAlias?: UmbControllerAlias, + ) { + super(host, ctrlAlias); this.#initialPathToMatch = initialPathToMatch; this.#queryMethod = queryMethod; diff --git a/src/packages/core/validation/translators/validation-path-translator-base.controller.ts b/src/packages/core/validation/translators/validation-path-translator-base.controller.ts index 27dda12276..b9a9bacb8e 100644 --- a/src/packages/core/validation/translators/validation-path-translator-base.controller.ts +++ b/src/packages/core/validation/translators/validation-path-translator-base.controller.ts @@ -1,6 +1,6 @@ import { UMB_VALIDATION_CONTEXT } from '../index.js'; import type { UmbValidationMessageTranslator } from './validation-message-path-translator.interface.js'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; export abstract class UmbValidationPathTranslatorBase @@ -10,8 +10,8 @@ export abstract class UmbValidationPathTranslatorBase // protected _context?: typeof UMB_VALIDATION_CONTEXT.TYPE; - constructor(host: UmbControllerHost) { - super(host); + constructor(host: UmbControllerHost, ctrlAlias?: UmbControllerAlias) { + super(host, ctrlAlias); this.consumeContext(UMB_VALIDATION_CONTEXT, (context) => { this._context?.removeTranslator(this); diff --git a/src/packages/core/workspace/components/workspace-split-view/index.ts b/src/packages/core/workspace/components/workspace-split-view/index.ts index 1312849754..509b219054 100644 --- a/src/packages/core/workspace/components/workspace-split-view/index.ts +++ b/src/packages/core/workspace/components/workspace-split-view/index.ts @@ -1,2 +1,3 @@ export * from './workspace-split-view.context.js'; export * from './workspace-split-view.element.js'; +export * from './workspace-split-view-variant-selector.element.js'; diff --git a/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts index f32f98bc68..efbce1d52c 100644 --- a/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts +++ b/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts @@ -5,29 +5,43 @@ import { UUIInputEvent, type UUIPopoverContainerElement, } from '@umbraco-cms/backoffice/external/uui'; -import { css, html, nothing, customElement, state, query, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; -import type { UmbDocumentVariantOptionModel, UmbDocumentWorkspaceContext } from '@umbraco-cms/backoffice/document'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { + css, + html, + nothing, + customElement, + state, + query, + ifDefined, + type TemplateResult, +} from '@umbraco-cms/backoffice/external/lit'; +import { + UmbVariantId, + type UmbEntityVariantModel, + type UmbEntityVariantOptionModel, +} from '@umbraco-cms/backoffice/variant'; import { UMB_PROPERTY_DATASET_CONTEXT, isNameablePropertyDatasetContext } from '@umbraco-cms/backoffice/property'; import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbVariantState } from '@umbraco-cms/backoffice/utils'; import { UmbDataPathVariantQuery, umbBindToValidation } from '@umbraco-cms/backoffice/validation'; +import type { UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; const elementName = 'umb-workspace-split-view-variant-selector'; @customElement(elementName) -export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { +export class UmbWorkspaceSplitViewVariantSelectorElement< + VariantOptionModelType extends + UmbEntityVariantOptionModel = UmbEntityVariantOptionModel, +> extends UmbLitElement { @query('#variant-selector-popover') private _popoverElement?: UUIPopoverContainerElement; @state() - private _variantOptions: Array = []; + private _variantOptions: Array = []; @state() private _readOnlyStates: Array = []; - // TODO: Stop using document context specific ActiveVariant type. @state() _activeVariants: Array = []; @@ -41,7 +55,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { private _name?: string; @state() - private _activeVariant?: UmbDocumentVariantOptionModel; + private _activeVariant?: VariantOptionModelType; @state() private _variantId?: UmbVariantId; @@ -52,11 +66,9 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { @state() private _readOnlyCultures: string[] = []; - #publishStateLocalizationMap = { - [DocumentVariantStateModel.DRAFT]: 'content_unpublished', - [DocumentVariantStateModel.PUBLISHED]: 'content_published', - [DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES]: 'content_publishedPendingChanges', - [DocumentVariantStateModel.NOT_CREATED]: 'content_notCreated', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected _variantSorter = (a: VariantOptionModelType, b: VariantOptionModelType) => { + return 0; }; constructor() { @@ -65,9 +77,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { this.consumeContext(UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, (instance) => { this.#splitViewContext = instance; - // NOTICE: This is hacky (the TypeScript casting), we can only accept doing this so far because we currently only use the Variant Selector on Document Workspace. [NL] - // This would need a refactor to enable the code below to work with different ContentTypes. Main problem here is the state, which is not generic for them all. [NL] - const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbDocumentWorkspaceContext; + const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbContentWorkspaceContext; if (!workspaceContext) throw new Error('Split View Workspace context not found'); this.#observeVariants(workspaceContext); @@ -83,18 +93,18 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { }); } - async #observeVariants(workspaceContext: UmbDocumentWorkspaceContext) { + async #observeVariants(workspaceContext: UmbContentWorkspaceContext) { this.observe( workspaceContext.variantOptions, (variantOptions) => { - this._variantOptions = variantOptions; + this._variantOptions = (variantOptions as Array).sort(this._variantSorter); this.#setReadOnlyCultures(); }, '_observeVariantOptions', ); } - async #observeReadOnlyStates(workspaceContext: UmbDocumentWorkspaceContext) { + async #observeReadOnlyStates(workspaceContext: UmbContentWorkspaceContext) { this.observe( workspaceContext.readOnlyState.states, (states) => { @@ -105,7 +115,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { ); } - async #observeActiveVariants(workspaceContext: UmbDocumentWorkspaceContext) { + async #observeActiveVariants(workspaceContext: UmbContentWorkspaceContext) { this.observe( workspaceContext.splitView.activeVariantsInfo, (activeVariants) => { @@ -131,7 +141,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { async #observeCurrentVariant() { if (!this.#datasetContext || !this.#splitViewContext) return; - const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbDocumentWorkspaceContext; + const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbContentWorkspaceContext; if (!workspaceContext) return; this._variantId = this.#datasetContext.getVariantId(); @@ -140,7 +150,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { workspaceContext.variantOptions, (options) => { const option = options.find((option) => option.language.unique === this._variantId?.culture); - this._activeVariant = option; + this._activeVariant = option as VariantOptionModelType; }, '_currentLanguage', ); @@ -160,11 +170,11 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { } } - #switchVariant(variant: UmbDocumentVariantOptionModel) { + #switchVariant(variant: VariantOptionModelType) { this.#splitViewContext?.switchVariant(UmbVariantId.Create(variant)); } - #openSplitView(variant: UmbDocumentVariantOptionModel) { + #openSplitView(variant: VariantOptionModelType) { this.#splitViewContext?.openSplitView(UmbVariantId.Create(variant)); } @@ -176,7 +186,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { return culture !== null ? this._activeVariantsCultures.includes(culture) : true; } - #isCreateMode(variantOption: UmbDocumentVariantOptionModel) { + #isCreateMode(variantOption: VariantOptionModelType) { return !variantOption.variant && !this.#isVariantActive(variantOption.culture); } @@ -231,7 +241,8 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { compact slot="append" popovertarget="variant-selector-popover" - title=${ifDefined(this._activeVariant?.language.name)}> + title=${ifDefined(this._activeVariant?.language.name)} + label="Select a variant"> ${this._activeVariant?.language.name} ${this.#renderReadOnlyTag(this._activeVariant?.culture)} @@ -270,7 +281,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { : nothing; } - #renderListItem(variantOption: UmbDocumentVariantOptionModel) { + #renderListItem(variantOption: VariantOptionModelType) { return html`
  • ${this.#renderSplitViewButton(variantOption)}
  • `; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected _renderVariantDetails(variantOption: VariantOptionModelType): TemplateResult { + return html``; + } + #isReadOnly(culture: string | null) { if (!culture) return false; return this._readOnlyCultures.includes(culture); @@ -312,16 +328,17 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { : nothing; } - #renderSplitViewButton(variant: UmbDocumentVariantOptionModel) { + #renderSplitViewButton(variant: VariantOptionModelType) { return html` ${this.#isVariantActive(variant.culture) ? nothing : html` this.#openSplitView(variant)}> - Split view + Open in Split view `} `; @@ -409,6 +426,32 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { background: var(--uui-palette-sand); color: var(--uui-palette-space-cadet-light); } + .variant-selector-switch-button .variant-info { + flex-grow: 1; + } + + .variant-selector-switch-button .variant-details { + color: var(--uui-color-text-alt); + font-size: 12px; + font-weight: normal; + } + .variant-selector-switch-button .variant-details { + color: var(--uui-color-text-alt); + font-size: 12px; + font-weight: normal; + } + .variant-selector-switch-button.add-mode .variant-details { + color: var(--uui-palette-dusty-grey-dark); + } + + .variant-selector-switch-button .specs-info { + color: var(--uui-color-text-alt); + font-size: 12px; + font-weight: normal; + } + .variant-selector-switch-button.add-mode .specs-info { + color: var(--uui-palette-dusty-grey-dark); + } .variant-selector-switch-button i { font-weight: normal; @@ -452,12 +495,6 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { bottom: 1px; display: none; } - - .variant-publish-state { - color: var(--uui-palette-malibu-dimmed); - font-size: 12px; - font-weight: normal; - } `, ]; } diff --git a/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts b/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts index 0c3209b02b..d205654112 100644 --- a/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts +++ b/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts @@ -1,5 +1,5 @@ import { UmbWorkspaceSplitViewContext } from './workspace-split-view.context.js'; -import { css, html, customElement, property, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, property, ifDefined, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -31,8 +31,15 @@ export class UmbWorkspaceSplitViewElement extends UmbLitElement { return this.splitViewContext.getSplitViewIndex()!; } + @state() + private _variantSelectorSlotHasContent = false; + splitViewContext = new UmbWorkspaceSplitViewContext(this); + #onVariantSelectorSlotChanged(e: Event) { + this._variantSelectorSlotHasContent = (e.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0; + } + override render() { return html` - + + ${this._variantSelectorSlotHasContent + ? nothing + : html``} + + ${this.displayNavigation ? html`` : ''} @@ -67,6 +77,7 @@ export class UmbWorkspaceSplitViewElement extends UmbLitElement { #header { flex: 1 1 auto; + display: block; } `, ]; diff --git a/src/packages/core/workspace/conditions/const.ts b/src/packages/core/workspace/conditions/const.ts index 00c36c7222..8a385d17ae 100644 --- a/src/packages/core/workspace/conditions/const.ts +++ b/src/packages/core/workspace/conditions/const.ts @@ -24,4 +24,3 @@ export const UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION = UMB_WORKSPACE_ENTITY_IS_NEW * Workspace alias condition alias */ export const UMB_WORKSPACE_CONDITION_ALIAS = 'Umb.Condition.WorkspaceAlias'; - diff --git a/src/packages/core/workspace/conditions/types.ts b/src/packages/core/workspace/conditions/types.ts index e13151df82..a9a21e6ea9 100644 --- a/src/packages/core/workspace/conditions/types.ts +++ b/src/packages/core/workspace/conditions/types.ts @@ -1,21 +1,24 @@ -import type { UMB_WORKSPACE_CONDITION_ALIAS, UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS } from './const.js'; +import type { + UMB_WORKSPACE_CONDITION_ALIAS, + UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, + UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS, +} from './const.js'; import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; -export interface WorkspaceAliasConditionConfig - extends UmbConditionConfigBase { - /** - * Define the workspace that this extension should be available in - * @example - * "Umb.Workspace.Document" - */ - match?: string; - /** - * Define one or more workspaces that this extension should be available in - * @example - * ["Umb.Workspace.Document", "Umb.Workspace.Media"] - */ - oneOf?: Array; - } +export interface WorkspaceAliasConditionConfig extends UmbConditionConfigBase { + /** + * Define the workspace that this extension should be available in + * @example + * "Umb.Workspace.Document" + */ + match?: string; + /** + * Define one or more workspaces that this extension should be available in + * @example + * ["Umb.Workspace.Document", "Umb.Workspace.Media"] + */ + oneOf?: Array; +} export type WorkspaceContentTypeAliasConditionConfig = UmbConditionConfigBase<'Umb.Condition.WorkspaceContentTypeAlias'> & { diff --git a/src/packages/core/workspace/conditions/workspace-alias.condition.ts b/src/packages/core/workspace/conditions/workspace-alias.condition.ts index 7e6c421ebf..71fe226054 100644 --- a/src/packages/core/workspace/conditions/workspace-alias.condition.ts +++ b/src/packages/core/workspace/conditions/workspace-alias.condition.ts @@ -1,10 +1,10 @@ import { UMB_WORKSPACE_CONTEXT } from '../workspace.context-token.js'; import type { UmbWorkspaceContext } from '../workspace-context.interface.js'; import type { WorkspaceAliasConditionConfig } from './types.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from './const.js'; import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from './const.js'; export class UmbWorkspaceAliasCondition extends UmbConditionBase diff --git a/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts b/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts index 5a3e30e821..855370f93a 100644 --- a/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts +++ b/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts @@ -2,7 +2,7 @@ import { UmbSubmittableWorkspaceContextBase } from '../submittable/index.js'; import { UmbEntityWorkspaceDataManager } from '../entity/entity-workspace-data-manager.js'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbEntityModel, UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; +import { UmbEntityContext, type UmbEntityModel, type UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; import { UMB_DISCARD_CHANGES_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { @@ -13,12 +13,17 @@ import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-ap import { umbExtensionsRegistry, type ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbDetailRepository } from '@umbraco-cms/backoffice/repository'; -export interface UmbEntityWorkspaceContextArgs { +export interface UmbEntityDetailWorkspaceContextArgs { entityType: string; workspaceAlias: string; detailRepositoryAlias: string; } +/** + * @deprecated Use UmbEntityDetailWorkspaceContextArgs instead + */ +export type UmbEntityWorkspaceContextArgs = UmbEntityDetailWorkspaceContextArgs; + export interface UmbEntityDetailWorkspaceContextCreateArgs { parent: UmbEntityModel; preset?: Partial; @@ -42,9 +47,9 @@ export abstract class UmbEntityDetailWorkspaceContextBase< public readonly unique = this._data.createObservablePartOfCurrent((data) => data?.unique); protected _getDataPromise?: Promise; - protected _detailRepository?: DetailRepositoryType; + #entityContext = new UmbEntityContext(this); #entityType: string; #parent = new UmbObjectState<{ entityType: string; unique: UmbEntityUnique } | undefined>(undefined); @@ -65,23 +70,58 @@ export abstract class UmbEntityDetailWorkspaceContextBase< constructor(host: UmbControllerHost, args: UmbEntityWorkspaceContextArgs) { super(host, args.workspaceAlias); this.#entityType = args.entityType; + this.#entityContext.setEntityType(this.#entityType); window.addEventListener('willchangestate', this.#onWillNavigate); this.#observeRepository(args.detailRepositoryAlias); } - getEntityType() { + /** + * Get the entity type + * @returns { string } The entity type + */ + getEntityType(): string { return this.#entityType; } - getData() { + /** + * Get the current data + * @returns { DetailModelType | undefined } The entity context + */ + getData(): DetailModelType | undefined { return this._data.getCurrent(); } - getUnique() { + /** + * Get the unique + * @returns { string | undefined } The unique identifier + */ + getUnique(): UmbEntityUnique | undefined { return this._data.getCurrent()?.unique; } + /** + * Get the parent + * @returns { UmbEntityModel | undefined } The parent entity + */ + getParent(): UmbEntityModel | undefined { + return this.#parent.getValue(); + } + + /** + * Get the parent unique + * @returns { string | undefined } The parent unique identifier + */ + getParentUnique(): UmbEntityUnique | undefined { + return this.#parent.getValue()?.unique; + } + + getParentEntityType() { + return this.#parent.getValue()?.entityType; + } + async load(unique: string) { + this.#entityContext.setEntityType(this.#entityType); + this.#entityContext.setUnique(unique); await this.#init; this.resetState(); this._getDataPromise = this._detailRepository!.requestByUnique(unique); @@ -90,18 +130,32 @@ export abstract class UmbEntityDetailWorkspaceContextBase< const data = response.data; if (data) { - this.setIsNew(false); this._data.setPersisted(data); this._data.setCurrent(data); + this.setIsNew(false); } return response; } - public isLoaded() { + /** + * Method to check if the workspace data is loaded. + * @returns { Promise | undefined } true if the workspace data is loaded. + * @memberof UmbEntityWorkspaceContextBase + */ + public isLoaded(): Promise | undefined { return this._getDataPromise; } + /** + * Create a data scaffold + * @param {CreateArgsType} args The arguments to create the scaffold. + * @param {UmbEntityModel} args.parent The parent entity. + * @param {UmbEntityUnique} args.parent.unique The unique identifier of the parent entity. + * @param {string} args.parent.entityType The entity type of the parent entity. + * @param {Partial} args.preset The preset data. + * @returns { Promise | undefined } The data of the scaffold. + */ async createScaffold(args: CreateArgsType) { await this.#init; this.resetState(); @@ -111,12 +165,16 @@ export abstract class UmbEntityDetailWorkspaceContextBase< let { data } = await request; if (!data) return undefined; + this.#entityContext.setEntityType(this.#entityType); + this.#entityContext.setUnique(data.unique); + if (this.modalContext) { data = { ...data, ...this.modalContext.data.preset }; } this.setIsNew(true); this._data.setPersisted(data); this._data.setCurrent(data); + return data; } @@ -133,32 +191,39 @@ export abstract class UmbEntityDetailWorkspaceContextBase< } if (this.getIsNew()) { - this.#create(currentData); + await this.#create(currentData); } else { - this.#update(currentData); + await this.#update(currentData); } } + /** + * Deletes the entity. + * @param unique The unique identifier of the entity to delete. + */ async delete(unique: string) { await this.#init; await this._detailRepository!.delete(unique); } /** - * @description method to check if the workspace is about to navigate away. + * Check if the workspace is about to navigate away. * @protected - * @param {string} newUrl - * @returns {*} + * @param {string} newUrl The new url that the workspace is navigating to. + * @returns { boolean} true if the workspace is navigating away. * @memberof UmbEntityWorkspaceContextBase */ - protected _checkWillNavigateAway(newUrl: string) { + protected _checkWillNavigateAway(newUrl: string): boolean { return !newUrl.includes(this.routes.getActiveLocalPath()); } async #create(currentData: DetailModelType) { + if (!this._detailRepository) throw new Error('Detail repository is not set'); + const parent = this.#parent.getValue(); if (!parent) throw new Error('Parent is not set'); - const { error, data } = await this._detailRepository!.create(currentData, parent.unique); + + const { error, data } = await this._detailRepository.create(currentData, parent.unique); if (error || !data) { throw error?.message ?? 'Repository did not return data after create.'; } @@ -252,6 +317,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase< public override destroy(): void { window.removeEventListener('willchangestate', this.#onWillNavigate); this._detailRepository?.destroy(); + this.#entityContext.destroy(); super.destroy(); } } diff --git a/src/packages/data-type/tree/folder/workspace/manifests.ts b/src/packages/data-type/tree/folder/workspace/manifests.ts index 78447f41ce..f0aa6a013c 100644 --- a/src/packages/data-type/tree/folder/workspace/manifests.ts +++ b/src/packages/data-type/tree/folder/workspace/manifests.ts @@ -1,7 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DATA_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_DATA_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/data-type/workspace/data-type-workspace.context.ts b/src/packages/data-type/workspace/data-type-workspace.context.ts index b29b1beac2..243e17d46b 100644 --- a/src/packages/data-type/workspace/data-type-workspace.context.ts +++ b/src/packages/data-type/workspace/data-type-workspace.context.ts @@ -91,10 +91,10 @@ export class UmbDataTypeWorkspaceContext { path: 'create/parent/:entityType/:parentUnique', component: UmbDataTypeWorkspaceEditorElement, - setup: (_component, info) => { + setup: async (_component, info) => { const parentEntityType = info.match.params.entityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; - this.createScaffold({ parent: { entityType: parentEntityType, unique: parentUnique } }); + await this.createScaffold({ parent: { entityType: parentEntityType, unique: parentUnique } }); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/data-type/workspace/manifests.ts b/src/packages/data-type/workspace/manifests.ts index c83c6cb153..61c6fb095f 100644 --- a/src/packages/data-type/workspace/manifests.ts +++ b/src/packages/data-type/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DATA_TYPE_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/dictionary/entity-action/manifests.ts b/src/packages/dictionary/entity-action/manifests.ts index 8b70942952..66c2670c34 100644 --- a/src/packages/dictionary/entity-action/manifests.ts +++ b/src/packages/dictionary/entity-action/manifests.ts @@ -8,7 +8,7 @@ export const manifests: Array = [ kind: 'default', alias: 'Umb.EntityAction.Dictionary.Create', name: 'Create Dictionary Entity Action', - weight: 600, + weight: 1200, api: () => import('./create/create.action.js'), forEntityTypes: [UMB_DICTIONARY_ENTITY_TYPE, UMB_DICTIONARY_ROOT_ENTITY_TYPE], meta: { diff --git a/src/packages/dictionary/menu-item/manifests.ts b/src/packages/dictionary/menu-item/manifests.ts index f3a87bd76a..03da8a1d40 100644 --- a/src/packages/dictionary/menu-item/manifests.ts +++ b/src/packages/dictionary/menu-item/manifests.ts @@ -1,6 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DICTIONARY_ENTITY_TYPE } from '../entity.js'; import { UMB_DICTIONARY_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_TRANSLATION_MENU_ALIAS } from '@umbraco-cms/backoffice/translation'; export const manifests: Array = [ diff --git a/src/packages/dictionary/workspace/dictionary-workspace.context.ts b/src/packages/dictionary/workspace/dictionary-workspace.context.ts index e85b75a67f..b329909230 100644 --- a/src/packages/dictionary/workspace/dictionary-workspace.context.ts +++ b/src/packages/dictionary/workspace/dictionary-workspace.context.ts @@ -32,7 +32,7 @@ export class UmbDictionaryWorkspaceContext setup: async (_component, info) => { const parentEntityType = info.match.params.entityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; - this.createScaffold({ parent: { entityType: parentEntityType, unique: parentUnique } }); + await this.createScaffold({ parent: { entityType: parentEntityType, unique: parentUnique } }); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/dictionary/workspace/manifests.ts b/src/packages/dictionary/workspace/manifests.ts index a78e4a8ef3..7acd6dbcd7 100644 --- a/src/packages/dictionary/workspace/manifests.ts +++ b/src/packages/dictionary/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DICTIONARY_ENTITY_TYPE } from '../entity.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const UMB_DICTIONARY_WORKSPACE_ALIAS = 'Umb.Workspace.Dictionary'; diff --git a/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts b/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts index 52f6f4e34f..59a6f5ef40 100644 --- a/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts +++ b/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts @@ -1,7 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_BLUEPRINT_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_DOCUMENT_BLUEPRINT_FOLDER_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts b/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts index c3b5b3264f..45f5ade7b2 100644 --- a/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts +++ b/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts @@ -1,190 +1,52 @@ import { UmbDocumentBlueprintPropertyDatasetContext } from '../property-dataset-context/document-blueprint-property-dataset-context.js'; import { UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE } from '../entity.js'; -import { UmbDocumentBlueprintDetailRepository } from '../repository/index.js'; -import type { - UmbDocumentBlueprintDetailModel, - UmbDocumentBlueprintValueModel, - UmbDocumentBlueprintVariantModel, - UmbDocumentBlueprintVariantOptionModel, -} from '../types.js'; -import { sortVariants } from '../utils.js'; +import type { UmbDocumentBlueprintDetailRepository } from '../repository/index.js'; +import { UMB_DOCUMENT_BLUEPRINT_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; +import type { UmbDocumentBlueprintDetailModel, UmbDocumentBlueprintVariantModel } from '../types.js'; import { UMB_CREATE_DOCUMENT_BLUEPRINT_WORKSPACE_PATH_PATTERN } from '../paths.js'; import { UMB_DOCUMENT_BLUEPRINT_WORKSPACE_ALIAS } from './manifests.js'; import { - appendToFrozenArray, - mergeObservables, - UmbArrayState, - UmbObjectState, -} from '@umbraco-cms/backoffice/observable-api'; -import { - UmbSubmittableWorkspaceContextBase, UmbWorkspaceIsNewRedirectController, UmbWorkspaceIsNewRedirectControllerAlias, - UmbWorkspaceSplitViewManager, } from '@umbraco-cms/backoffice/workspace'; -import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; import { type UmbDocumentTypeDetailModel, UmbDocumentTypeDetailRepository, } from '@umbraco-cms/backoffice/document-type'; -import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; -import { - UmbRequestReloadChildrenOfEntityEvent, - UmbRequestReloadStructureForEntityEvent, -} from '@umbraco-cms/backoffice/entity-action'; -import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; -import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import { UmbContentWorkspaceDataManager, type UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; -import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; +import { UmbContentDetailWorkspaceContextBase, type UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; import { UMB_DOCUMENT_COLLECTION_ALIAS, UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD, UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN, } from '@umbraco-cms/backoffice/document'; -import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; -import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import { map } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; -import { UMB_SETTINGS_SECTION_PATH } from '@umbraco-cms/backoffice/settings'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; -type EntityModel = UmbDocumentBlueprintDetailModel; +type ContentModel = UmbDocumentBlueprintDetailModel; +type ContentTypeModel = UmbDocumentTypeDetailModel; export class UmbDocumentBlueprintWorkspaceContext - extends UmbSubmittableWorkspaceContextBase - implements UmbContentWorkspaceContext + extends UmbContentDetailWorkspaceContextBase< + ContentModel, + UmbDocumentBlueprintDetailRepository, + ContentTypeModel, + UmbDocumentBlueprintVariantModel + > + implements UmbContentWorkspaceContext { - readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const; - // - readonly repository = new UmbDocumentBlueprintDetailRepository(this); - - #parent = new UmbObjectState<{ entityType: string; unique: string | null } | undefined>(undefined); - readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined)); - readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined)); - - readonly #data = new UmbContentWorkspaceDataManager(this, UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD); - - #getDataPromise?: Promise; - // TODO: Optimize this so it uses either a App Language Context? [NL] - #languageRepository = new UmbLanguageCollectionRepository(this); - #languages = new UmbArrayState([], (x) => x.unique); - public readonly languages = this.#languages.asObservable(); - - public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); - - public isLoaded() { - return this.#getDataPromise; - } - - readonly unique = this.#data.createObservablePartOfCurrent((data) => data?.unique); - readonly entityType = this.#data.createObservablePartOfCurrent((data) => data?.entityType); - - readonly contentTypeUnique = this.#data.createObservablePartOfCurrent((data) => data?.documentType.unique); - - readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants || []); - - readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); - getValues() { - return this.#data.getCurrent()?.values; - } - - //readonly urls = this.#data.current.asObservablePart((data) => data?.urls || []); - - readonly structure = new UmbContentTypeStructureManager(this, new UmbDocumentTypeDetailRepository(this)); - readonly variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); - readonly variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); - readonly varies = this.structure.ownerContentTypeObservablePart((x) => - x ? x.variesByCulture || x.variesBySegment : undefined, - ); - #varies?: boolean; - #variesByCulture?: boolean; - #variesBySegment?: boolean; - - readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); - #dataTypeSchemaAliasMap = new Map(); - - readonly splitView = new UmbWorkspaceSplitViewManager(); - - readonly variantOptions = mergeObservables( - [this.varies, this.variants, this.languages], - ([varies, variants, languages]) => { - // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL] - if (varies === true) { - return languages.map((language) => { - return { - variant: variants.find((x) => x.culture === language.unique), - language, - // TODO: When including segments, this object should be updated to include a object for the segment. [NL] - // TODO: When including segments, the unique should be updated to include the segment as well. [NL] - unique: language.unique, // This must be a variantId string! - culture: language.unique, - segment: null, - } as UmbDocumentBlueprintVariantOptionModel; - }); - } else if (varies === false) { - return [ - { - variant: variants.find((x) => x.culture === null), - language: languages.find((x) => x.isDefault), - culture: null, - segment: null, - unique: UMB_INVARIANT_CULTURE, // This must be a variantId string! - } as UmbDocumentBlueprintVariantOptionModel, - ]; - } - return []; - }, - ).pipe(map((results) => results.sort(sortVariants))); - - // TODO: this should be set up for all entity workspace contexts in a base class - #entityContext = new UmbEntityContext(this); + readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.documentType.unique); constructor(host: UmbControllerHost) { - super(host, UMB_DOCUMENT_BLUEPRINT_WORKSPACE_ALIAS); - - this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); - this.observe( - this.varies, - (varies) => { - this.#data.setVaries(varies); - this.#varies = varies; - }, - null, - ); - this.observe( - this.variesByCulture, - (varies) => { - this.#data.setVariesByCulture(varies); - this.#variesByCulture = varies; - }, - null, - ); - this.observe( - this.variesBySegment, - (varies) => { - this.#data.setVariesBySegment(varies); - this.#variesBySegment = varies; - }, - null, - ); - this.observe( - this.structure.contentTypeDataTypeUniques, - (dataTypeUniques: Array) => { - this.#dataTypeItemManager.setUniques(dataTypeUniques); - }, - null, - ); - this.observe(this.#dataTypeItemManager.items, (dataTypes) => { - // Make a map of the data type unique and editorAlias: - this.#dataTypeSchemaAliasMap = new Map( - dataTypes.map((dataType) => { - return [dataType.unique, dataType.propertyEditorSchemaAlias]; - }), - ); + super(host, { + entityType: UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE, + workspaceAlias: UMB_DOCUMENT_BLUEPRINT_WORKSPACE_ALIAS, + detailRepositoryAlias: UMB_DOCUMENT_BLUEPRINT_DETAIL_REPOSITORY_ALIAS, + contentTypeDetailRepository: UmbDocumentTypeDetailRepository, + contentVariantScaffold: UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD, }); - this.loadLanguages(); + this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); this.routes.setRoutes([ { @@ -194,7 +56,7 @@ export class UmbDocumentBlueprintWorkspaceContext const parentEntityType = info.match.params.parentEntityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; const documentTypeUnique = info.match.params.documentTypeUnique; - this.create({ entityType: parentEntityType, unique: parentUnique }, documentTypeUnique); + await this.create({ entityType: parentEntityType, unique: parentUnique }, documentTypeUnique); new UmbWorkspaceIsNewRedirectController( this, @@ -215,251 +77,39 @@ export class UmbDocumentBlueprintWorkspaceContext ]); } - override resetState() { - super.resetState(); - this.#data.clear(); - } - - async loadLanguages() { - // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] - const { data } = await this.#languageRepository.requestCollection({}); - this.#languages.setValue(data?.items ?? []); - } - - async load(unique: string) { - this.resetState(); - this.#getDataPromise = this.repository.requestByUnique(unique); - type GetDataType = Awaited>; - const { data, asObservable } = (await this.#getDataPromise) as GetDataType; - - if (data) { - this.#entityContext.setEntityType(UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE); - this.#entityContext.setUnique(unique); - this.setIsNew(false); - this.#data.setPersisted(data); - this.#data.setCurrent(data); - } - - this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'UmbDocumentBlueprintStoreObserver'); - } - - #onStoreChange(entity: EntityModel | undefined) { - if (!entity) { - //TODO: This solution is alright for now. But reconsider when we introduce signal-r - history.pushState(null, '', UMB_SETTINGS_SECTION_PATH); - } - } - async create(parent: UmbEntityModel, documentTypeUnique: string) { - this.resetState(); - this.#parent.setValue(parent); - - this.#getDataPromise = this.repository.createScaffold({ - documentType: { unique: documentTypeUnique, collection: null }, + return this.createScaffold({ + parent, + preset: { + documentType: { + unique: documentTypeUnique, + collection: null, + }, + }, }); - - const { data } = await this.#getDataPromise; - if (!data) return undefined; - - this.#entityContext.setEntityType(UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE); - this.#entityContext.setUnique(data.unique); - this.setIsNew(true); - this.#data.setPersisted(undefined); - this.#data.setCurrent(data); - return data; } getCollectionAlias() { return UMB_DOCUMENT_COLLECTION_ALIAS; } - getData() { - return this.#data.getCurrent(); - } - - getUnique() { - return this.getData()?.unique; - } - - getEntityType() { - return UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE; - } - - getContentTypeId() { - return this.getData()?.documentType.unique; - } - - getVaries() { - return this.#varies; - } - getVariesByCulture() { - return this.#variesByCulture; - } - getVariesBySegment() { - return this.#variesBySegment; - } - - variantById(variantId: UmbVariantId) { - return this.#data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x))); - } - - getVariant(variantId: UmbVariantId) { - return this.#data.getCurrent()?.variants?.find((x) => variantId.compare(x)); - } - - getName(variantId?: UmbVariantId) { - const variants = this.#data.getCurrent()?.variants; - if (!variants) return; - if (variantId) { - return variants.find((x) => variantId.compare(x))?.name; - } else { - return variants[0]?.name; - } - } - - setName(name: string, variantId?: UmbVariantId) { - this.#data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); - } - - name(variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '', - ); - } - - async propertyStructureById(propertyId: string) { - return this.structure.propertyStructureById(propertyId); - } /** - * @function propertyValueByAlias - * @param variantId - * @param {string} propertyAlias - * @returns {Promise | undefined>} - * @description Get an Observable for the value of this property. + * Gets the unique identifier of the content type. + * @deprecated Use `getContentTypeUnique` instead. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbDocumentWorkspaceContext */ - async propertyValueByAlias( - propertyAlias: string, - variantId?: UmbVariantId, - ): Promise | undefined> { - return this.#data.createObservablePartOfCurrent( - (data) => - data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x as any) : true)) - ?.value as PropertyValueType, - ); + getContentTypeId(): string | undefined { + return this.getContentTypeUnique(); } /** - * Get the current value of the property with the given alias and variantId. - * @param alias - * @param variantId - * @returns The value or undefined if not set or found. + * Gets the unique identifier of the content type. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbDocumentWorkspaceContext */ - getPropertyValue(alias: string, variantId?: UmbVariantId) { - const currentData = this.#data.getCurrent(); - if (currentData) { - const newDataSet = currentData.values?.find( - (x) => x.alias === alias && (variantId ? variantId.compare(x as any) : true), - ); - return newDataSet?.value as ReturnType; - } - return undefined; - } - async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { - this.initiatePropertyValueChange(); - variantId ??= UmbVariantId.CreateInvariant(); - const property = await this.structure.getPropertyStructureByAlias(alias); - - if (!property) { - throw new Error(`Property alias "${alias}" not found.`); - } - - const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); - if (!editorAlias) { - throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); - } - - const entry = { ...variantId.toObject(), alias, editorAlias, value } as UmbDocumentBlueprintValueModel; - - const currentData = this.getData(); - if (currentData) { - const values = appendToFrozenArray( - currentData.values ?? [], - entry, - (x) => x.alias === alias && variantId!.compare(x), - ); - this.#data.updateCurrent({ values }); - - // TODO: We should move this type of logic to the act of saving [NL] - this.#data.ensureVariantData(variantId); - } - this.finishPropertyValueChange(); - } - - initiatePropertyValueChange() { - this.#data.initiatePropertyValueChange(); - } - finishPropertyValueChange = () => { - this.#data.finishPropertyValueChange(); - }; - - async #handleSave() { - const current = this.#data.getCurrent(); - if (!current?.unique) throw new Error('Unique is missing'); - - if (this.getIsNew()) { - const parent = this.#parent.getValue(); - if (!parent) throw new Error('Parent is not set'); - - const { data, error } = await this.repository.create(current, parent.unique); - if (!data || error) { - console.error('Error creating document', error); - throw new Error('Error creating document'); - } - - this.setIsNew(false); - this.#data.setPersisted(data); - // We do not know about the variant IDs, so lets update everything. - this.#data.setCurrent(data); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadChildrenOfEntityEvent({ - entityType: parent.entityType, - unique: parent.unique, - }); - eventContext.dispatchEvent(event); - } else { - // Save: - const { data, error } = await this.repository.save(current); - if (!data || error) { - console.error('Error saving document', error); - throw new Error('Error saving document'); - } - - this.#data.setPersisted(data); - // We do not know about the variant IDs, so lets update everything. - this.#data.setCurrent(data); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - unique: this.getUnique()!, - entityType: this.getEntityType(), - }); - - eventContext.dispatchEvent(event); - } - } - - async submit() { - const data = this.getData(); - if (!data) throw new Error('Data is missing'); - await this.#handleSave(); - } - - async delete() { - const id = this.getUnique(); - if (id) { - await this.repository.delete(id); - } + getContentTypeUnique(): string | undefined { + return this.getData()?.documentType.unique; } public createPropertyDatasetContext( @@ -468,12 +118,6 @@ export class UmbDocumentBlueprintWorkspaceContext ): UmbDocumentBlueprintPropertyDatasetContext { return new UmbDocumentBlueprintPropertyDatasetContext(host, this, variantId); } - - public override destroy(): void { - this.structure.destroy(); - this.#languageRepository.destroy(); - super.destroy(); - } } export { UmbDocumentBlueprintWorkspaceContext as api }; diff --git a/src/packages/documents/document-blueprints/workspace/manifests.ts b/src/packages/documents/document-blueprints/workspace/manifests.ts index ba2884d0e4..06ec9abd48 100644 --- a/src/packages/documents/document-blueprints/workspace/manifests.ts +++ b/src/packages/documents/document-blueprints/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE } from '../entity.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const UMB_DOCUMENT_BLUEPRINT_WORKSPACE_ALIAS = 'Umb.Workspace.DocumentBlueprint'; diff --git a/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts b/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts index de53c7122e..f7bc27eead 100644 --- a/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts +++ b/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts @@ -1,5 +1,5 @@ import type { UmbDocumentTypeItemModel } from '../../repository/index.js'; -import { UMB_DOCUMENT_TYPE_WORKSPACE_MODAL } from '../../workspace/document-type-workspace.modal-token.js'; +import { UMB_DOCUMENT_TYPE_WORKSPACE_MODAL } from '../../workspace/document-type/document-type-workspace.modal-token.js'; import type { UmbDocumentTypeTreeItemModel } from '../../tree/types.js'; import { UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN } from '../../paths.js'; import { UmbDocumentTypePickerInputContext } from './input-document-type.context.js'; diff --git a/src/packages/documents/document-types/entity-actions/create/manifests.ts b/src/packages/documents/document-types/entity-actions/create/manifests.ts index 2879df41cf..59786bc3e6 100644 --- a/src/packages/documents/document-types/entity-actions/create/manifests.ts +++ b/src/packages/documents/document-types/entity-actions/create/manifests.ts @@ -1,4 +1,5 @@ -import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from '../../entity.js'; +import { UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from '../../entity.js'; +import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../tree/index.js'; export const manifests: Array = [ { diff --git a/src/packages/documents/document-types/entity.ts b/src/packages/documents/document-types/entity.ts index 7279881dac..3e502f8058 100644 --- a/src/packages/documents/document-types/entity.ts +++ b/src/packages/documents/document-types/entity.ts @@ -1,10 +1,10 @@ +import type { UmbDocumentTypeFolderEntityType } from './tree/index.js'; + export const UMB_DOCUMENT_TYPE_ENTITY_TYPE = 'document-type'; export const UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE = 'document-type-root'; -export const UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE = 'document-type-folder'; export type UmbDocumentTypeEntityType = typeof UMB_DOCUMENT_TYPE_ENTITY_TYPE; export type UmbDocumentTypeRootEntityType = typeof UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE; -export type UmbDocumentTypeFolderEntityType = typeof UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE; export type UmbDocumentTypeEntityTypeUnion = | UmbDocumentTypeEntityType diff --git a/src/packages/documents/document-types/menu/manifests.ts b/src/packages/documents/document-types/menu/manifests.ts index 73d7506420..35ea56aca9 100644 --- a/src/packages/documents/document-types/menu/manifests.ts +++ b/src/packages/documents/document-types/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_TYPE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/documents/document-types/modals/document-type-picker-modal.token.ts b/src/packages/documents/document-types/modals/document-type-picker-modal.token.ts index 9ab4a1ac6d..ca1cac6fad 100644 --- a/src/packages/documents/document-types/modals/document-type-picker-modal.token.ts +++ b/src/packages/documents/document-types/modals/document-type-picker-modal.token.ts @@ -1,10 +1,7 @@ +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from '../entity.js'; import { UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN } from '../paths.js'; +import type { UmbDocumentTypeTreeItemModel } from '../tree/index.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; -import { - UMB_DOCUMENT_TYPE_ENTITY_TYPE, - UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, - type UmbDocumentTypeTreeItemModel, -} from '@umbraco-cms/backoffice/document-type'; import { type UmbTreePickerModalValue, type UmbTreePickerModalData, diff --git a/src/packages/documents/document-types/tree/document-type.tree.server.data-source.ts b/src/packages/documents/document-types/tree/document-type.tree.server.data-source.ts index afd9e20e58..7f8dbfa1b7 100644 --- a/src/packages/documents/document-types/tree/document-type.tree.server.data-source.ts +++ b/src/packages/documents/document-types/tree/document-type.tree.server.data-source.ts @@ -1,9 +1,6 @@ -import { - UMB_DOCUMENT_TYPE_ENTITY_TYPE, - UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE, - UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, -} from '../entity.js'; +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from '../entity.js'; import type { UmbDocumentTypeTreeItemModel } from './types.js'; +import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from './folder/index.js'; import type { UmbTreeAncestorsOfRequestArgs, UmbTreeChildrenOfRequestArgs, diff --git a/src/packages/documents/document-types/tree/folder/entity.ts b/src/packages/documents/document-types/tree/folder/entity.ts new file mode 100644 index 0000000000..7b75b94cde --- /dev/null +++ b/src/packages/documents/document-types/tree/folder/entity.ts @@ -0,0 +1,2 @@ +export const UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE = 'document-type-folder'; +export type UmbDocumentTypeFolderEntityType = typeof UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE; diff --git a/src/packages/documents/document-types/tree/folder/index.ts b/src/packages/documents/document-types/tree/folder/index.ts index 388ec6aca4..8bf4a043c8 100644 --- a/src/packages/documents/document-types/tree/folder/index.ts +++ b/src/packages/documents/document-types/tree/folder/index.ts @@ -1,2 +1,3 @@ export * from './repository/index.js'; export * from './workspace/index.js'; +export * from './entity.js'; diff --git a/src/packages/documents/document-types/tree/folder/manifests.ts b/src/packages/documents/document-types/tree/folder/manifests.ts index 9715c8438e..c8a24ff157 100644 --- a/src/packages/documents/document-types/tree/folder/manifests.ts +++ b/src/packages/documents/document-types/tree/folder/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../entity.js'; -import { UMB_DOCUMENT_TYPE_FOLDER_REPOSITORY_ALIAS } from './repository/constants.js'; -import { manifests as workspaceManifests } from './workspace/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as workspaceManifests } from './workspace/manifests.js'; +import { UMB_DOCUMENT_TYPE_FOLDER_REPOSITORY_ALIAS } from './repository/constants.js'; +import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from './entity.js'; export const manifests: Array = [ { diff --git a/src/packages/documents/document-types/tree/folder/repository/document-type-folder.server.data-source.ts b/src/packages/documents/document-types/tree/folder/repository/document-type-folder.server.data-source.ts index 4c088664be..0c30b4d911 100644 --- a/src/packages/documents/document-types/tree/folder/repository/document-type-folder.server.data-source.ts +++ b/src/packages/documents/document-types/tree/folder/repository/document-type-folder.server.data-source.ts @@ -1,4 +1,4 @@ -import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; +import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../entity.js'; import type { UmbFolderModel } from '@umbraco-cms/backoffice/tree'; import { DocumentTypeService } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/packages/documents/document-types/tree/folder/types.ts b/src/packages/documents/document-types/tree/folder/types.ts index 20929ae03b..4c5a953a9d 100644 --- a/src/packages/documents/document-types/tree/folder/types.ts +++ b/src/packages/documents/document-types/tree/folder/types.ts @@ -1,5 +1,5 @@ -import type { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../entity.js'; import type { UmbDocumentTypeTreeItemModel } from '../types.js'; +import type { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from './entity.js'; export interface UmbDocumentTypeFolderTreeItemModel extends UmbDocumentTypeTreeItemModel { entityType: typeof UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE; diff --git a/src/packages/documents/document-types/tree/folder/workspace/document-type-folder-workspace.context.ts b/src/packages/documents/document-types/tree/folder/workspace/document-type-folder-workspace.context.ts index 81ee4598ca..670f2a1ddf 100644 --- a/src/packages/documents/document-types/tree/folder/workspace/document-type-folder-workspace.context.ts +++ b/src/packages/documents/document-types/tree/folder/workspace/document-type-folder-workspace.context.ts @@ -1,8 +1,8 @@ -import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_DOCUMENT_TYPE_FOLDER_REPOSITORY_ALIAS, type UmbDocumentTypeFolderRepository, } from '../repository/index.js'; +import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../entity.js'; import { UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js'; import { UmbDocumentTypeFolderWorkspaceEditorElement } from './document-type-folder-editor.element.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/packages/documents/document-types/tree/folder/workspace/document-type-folder.workspace.context-token.ts b/src/packages/documents/document-types/tree/folder/workspace/document-type-folder.workspace.context-token.ts index f81ff2e17c..8f59d8fd8f 100644 --- a/src/packages/documents/document-types/tree/folder/workspace/document-type-folder.workspace.context-token.ts +++ b/src/packages/documents/document-types/tree/folder/workspace/document-type-folder.workspace.context-token.ts @@ -1,4 +1,4 @@ -import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; +import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../entity.js'; import type { UmbDocumentTypeFolderWorkspaceContext } from './document-type-folder-workspace.context.js'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import type { UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; diff --git a/src/packages/documents/document-types/tree/folder/workspace/index.ts b/src/packages/documents/document-types/tree/folder/workspace/index.ts index e69de29bb2..14b140114e 100644 --- a/src/packages/documents/document-types/tree/folder/workspace/index.ts +++ b/src/packages/documents/document-types/tree/folder/workspace/index.ts @@ -0,0 +1,2 @@ +export * from './constants.js'; +export * from './paths.js'; diff --git a/src/packages/documents/document-types/tree/folder/workspace/manifests.ts b/src/packages/documents/document-types/tree/folder/workspace/manifests.ts index d0a5f120c4..884ed1b6d7 100644 --- a/src/packages/documents/document-types/tree/folder/workspace/manifests.ts +++ b/src/packages/documents/document-types/tree/folder/workspace/manifests.ts @@ -1,7 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; -import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; +import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../entity.js'; import { UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/documents/document-types/tree/folder/workspace/paths.ts b/src/packages/documents/document-types/tree/folder/workspace/paths.ts new file mode 100644 index 0000000000..5a9b113494 --- /dev/null +++ b/src/packages/documents/document-types/tree/folder/workspace/paths.ts @@ -0,0 +1,14 @@ +import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../entity.js'; +import { UmbPathPattern } from '@umbraco-cms/backoffice/router'; +import { UMB_SETTINGS_SECTION_PATHNAME } from '@umbraco-cms/backoffice/settings'; +import { UMB_WORKSPACE_PATH_PATTERN } from '@umbraco-cms/backoffice/workspace'; + +export const UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_PATH = UMB_WORKSPACE_PATH_PATTERN.generateAbsolute({ + sectionName: UMB_SETTINGS_SECTION_PATHNAME, + entityType: UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE, +}); + +export const UMB_EDIT_DOCUMENT_TYPE_FOLDER_WORKSPACE_PATH_PATTERN = new UmbPathPattern<{ unique: string }>( + 'edit/:unique', + UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_PATH, +); diff --git a/src/packages/documents/document-types/tree/index.ts b/src/packages/documents/document-types/tree/index.ts index f2f1e85a61..addb6f04a5 100644 --- a/src/packages/documents/document-types/tree/index.ts +++ b/src/packages/documents/document-types/tree/index.ts @@ -1,3 +1,5 @@ export { UMB_DOCUMENT_TYPE_TREE_STORE_CONTEXT } from './document-type.tree.store.context-token.js'; -export { UMB_DOCUMENT_TYPE_TREE_REPOSITORY_ALIAS, UMB_DOCUMENT_TYPE_TREE_ALIAS } from './constants.js'; +export * from './constants.js'; export * from './folder/index.js'; +export * from './tree-item-children/collection/index.js'; +export type * from './types.js'; diff --git a/src/packages/documents/document-types/tree/manifests.ts b/src/packages/documents/document-types/tree/manifests.ts index 40f1ed4830..47e012f97a 100644 --- a/src/packages/documents/document-types/tree/manifests.ts +++ b/src/packages/documents/document-types/tree/manifests.ts @@ -1,15 +1,14 @@ -import { - UMB_DOCUMENT_TYPE_ENTITY_TYPE, - UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE, - UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, -} from '../entity.js'; +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from '../entity.js'; +import { UMB_DOCUMENT_TYPE_ROOT_WORKSPACE_ALIAS } from '../workspace/document-type-root/index.js'; import { UMB_DOCUMENT_TYPE_TREE_ALIAS, UMB_DOCUMENT_TYPE_TREE_REPOSITORY_ALIAS, UMB_DOCUMENT_TYPE_TREE_STORE_ALIAS, } from './constants.js'; +import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE, UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS } from './folder/index.js'; import { manifests as folderManifests } from './folder/manifests.js'; -import { manifests as reloadManifests } from './reload-tree-item-children/manifests.js'; +import { manifests as treeItemChildrenManifests } from './tree-item-children/manifests.js'; +import { UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_ALIAS } from './tree-item-children/collection/index.js'; export const manifests: Array = [ { @@ -45,15 +44,23 @@ export const manifests: Array = [ ], }, { - type: 'workspace', - kind: 'default', - alias: 'Umb.Workspace.DocumentType.Root', - name: 'Document Type Root Workspace', + type: 'workspaceView', + kind: 'collection', + alias: 'Umb.WorkspaceView.DocumentType.TreeItemChildrenCollection', + name: 'Document Type Tree Item Children Collection Workspace View', meta: { - entityType: UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, - headline: '#treeHeaders_documentTypes', + label: '#general_design', + pathname: 'design', + icon: 'icon-member-dashed-line', + collectionAlias: UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_ALIAS, }, + conditions: [ + { + alias: 'Umb.Condition.WorkspaceAlias', + oneOf: [UMB_DOCUMENT_TYPE_ROOT_WORKSPACE_ALIAS, UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS], + }, + ], }, ...folderManifests, - ...reloadManifests, + ...treeItemChildrenManifests, ]; diff --git a/src/packages/documents/document-types/tree/tree-item-children/collection/constants.ts b/src/packages/documents/document-types/tree/tree-item-children/collection/constants.ts new file mode 100644 index 0000000000..906ddd8683 --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/collection/constants.ts @@ -0,0 +1 @@ +export const UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_ALIAS = 'Umb.Collection.DocumentType.TreeItemChildren'; diff --git a/src/packages/documents/document-types/tree/tree-item-children/collection/index.ts b/src/packages/documents/document-types/tree/tree-item-children/collection/index.ts new file mode 100644 index 0000000000..6c11f6abbb --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/collection/index.ts @@ -0,0 +1,2 @@ +export * from './constants.js'; +export * from './repository/index.js'; diff --git a/src/packages/documents/document-types/tree/tree-item-children/collection/manifests.ts b/src/packages/documents/document-types/tree/tree-item-children/collection/manifests.ts new file mode 100644 index 0000000000..8f03eaf7df --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/collection/manifests.ts @@ -0,0 +1,18 @@ +import { manifests as viewManifests } from './views/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; +import { UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_ALIAS } from './constants.js'; +import { UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_REPOSITORY_ALIAS } from './repository/index.js'; + +export const manifests: Array = [ + { + type: 'collection', + kind: 'default', + alias: UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_ALIAS, + name: 'Document Type Tree Item Children Collection', + meta: { + repositoryAlias: UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_REPOSITORY_ALIAS, + }, + }, + ...viewManifests, + ...repositoryManifests, +]; diff --git a/src/packages/documents/document-types/tree/tree-item-children/collection/repository/constants.ts b/src/packages/documents/document-types/tree/tree-item-children/collection/repository/constants.ts new file mode 100644 index 0000000000..2f9a0ac272 --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/collection/repository/constants.ts @@ -0,0 +1,2 @@ +export const UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_REPOSITORY_ALIAS = + 'Umb.Repository.DocumentType.TreeItemChildrenCollection'; diff --git a/src/packages/documents/document-types/tree/tree-item-children/collection/repository/document-type-tree-item-children-collection.repository.ts b/src/packages/documents/document-types/tree/tree-item-children/collection/repository/document-type-tree-item-children-collection.repository.ts new file mode 100644 index 0000000000..5968a3aef1 --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/collection/repository/document-type-tree-item-children-collection.repository.ts @@ -0,0 +1,33 @@ +import { UmbDocumentTypeTreeRepository } from '../../../document-type-tree.repository.js'; +import type { UmbCollectionFilterModel, UmbCollectionRepository } from '@umbraco-cms/backoffice/collection'; +import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; +import { UMB_ENTITY_CONTEXT, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export class UmbDocumentTypeTreeItemChildrenCollectionRepository + extends UmbRepositoryBase + implements UmbCollectionRepository +{ + #treeRepository = new UmbDocumentTypeTreeRepository(this); + + async requestCollection(filter: UmbCollectionFilterModel) { + // TODO: get parent from args + const entityContext = await this.getContext(UMB_ENTITY_CONTEXT); + if (!entityContext) throw new Error('Entity context not found'); + + const entityType = entityContext.getEntityType(); + const unique = entityContext.getUnique(); + + if (!entityType) throw new Error('Entity type not found'); + if (unique === undefined) throw new Error('Unique not found'); + + const parent: UmbEntityModel = { entityType, unique }; + + if (parent.unique === null) { + return this.#treeRepository.requestTreeRootItems({ skip: filter.skip, take: filter.take }); + } else { + return this.#treeRepository.requestTreeItemsOf({ parent, skip: filter.skip, take: filter.take }); + } + } +} + +export { UmbDocumentTypeTreeItemChildrenCollectionRepository as api }; diff --git a/src/packages/documents/document-types/tree/tree-item-children/collection/repository/index.ts b/src/packages/documents/document-types/tree/tree-item-children/collection/repository/index.ts new file mode 100644 index 0000000000..4f07201dcf --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/collection/repository/index.ts @@ -0,0 +1 @@ +export * from './constants.js'; diff --git a/src/packages/documents/document-types/tree/tree-item-children/collection/repository/manifests.ts b/src/packages/documents/document-types/tree/tree-item-children/collection/repository/manifests.ts new file mode 100644 index 0000000000..5b37fad314 --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/collection/repository/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_REPOSITORY_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'repository', + alias: UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_REPOSITORY_ALIAS, + name: 'Document Type Tree Item Children Collection Repository', + api: () => import('./document-type-tree-item-children-collection.repository.js'), + }, +]; diff --git a/src/packages/documents/document-types/tree/tree-item-children/collection/types.ts b/src/packages/documents/document-types/tree/tree-item-children/collection/types.ts new file mode 100644 index 0000000000..a45264779f --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/collection/types.ts @@ -0,0 +1,6 @@ +import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export interface UmbDocumentTypeTreeItemChildrenCollectionFilterModel extends UmbCollectionFilterModel { + parent: UmbEntityModel; +} diff --git a/src/packages/documents/document-types/tree/tree-item-children/collection/views/document-type-tree-item-table-collection-view.element.ts b/src/packages/documents/document-types/tree/tree-item-children/collection/views/document-type-tree-item-table-collection-view.element.ts new file mode 100644 index 0000000000..ff8f006f37 --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/collection/views/document-type-tree-item-table-collection-view.element.ts @@ -0,0 +1,135 @@ +import { UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN } from '../../../../paths.js'; +import { UMB_EDIT_DOCUMENT_TYPE_FOLDER_WORKSPACE_PATH_PATTERN } from '../../../folder/index.js'; +import type { UmbDocumentTypeTreeItemModel } from '../../../types.js'; +import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; +import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; +import type { UmbTableColumn, UmbTableConfig, UmbTableItem } from '@umbraco-cms/backoffice/components'; +import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbModalRouteRegistrationController, type UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; +import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; + +const elementName = 'umb-document-type-tree-item-table-collection-view'; +@customElement(elementName) +export class UmbDocumentTypeTreeItemTableCollectionViewElement extends UmbLitElement { + @state() + private _tableConfig: UmbTableConfig = { + allowSelection: false, + }; + + @state() + private _tableColumns: Array = [ + { + name: 'Name', + alias: 'name', + }, + { + name: 'Element Type', + alias: 'isElementType', + }, + { + name: '', + alias: 'entityActions', + }, + ]; + + @state() + private _tableItems: Array = []; + + #collectionContext?: UmbDefaultCollectionContext; + #routeBuilder?: UmbModalRouteBuilder; + + constructor() { + super(); + + this.consumeContext(UMB_COLLECTION_CONTEXT, (instance) => { + this.#collectionContext = instance; + }); + + this.#registerModalRoute(); + } + + #registerModalRoute() { + new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + .addAdditionalPath(':entityType') + .onSetup((params) => { + return { data: { entityType: params.entityType, preset: {} } }; + }) + .observeRouteBuilder((routeBuilder) => { + this.#routeBuilder = routeBuilder; + + // NOTE: Configuring the observations AFTER the route builder is ready, + // otherwise there is a race condition and `#collectionContext.items` tends to win. [LK] + this.#observeCollectionItems(); + }); + } + + #observeCollectionItems() { + if (!this.#collectionContext) return; + this.observe(this.#collectionContext.items, (items) => this.#createTableItems(items), 'umbCollectionItemsObserver'); + } + + #createTableItems(items: Array) { + const routeBuilder = this.#routeBuilder; + if (!routeBuilder) throw new Error('Route builder not ready'); + + this._tableItems = items.map((item) => { + const modalEditPath = + routeBuilder({ entityType: item.entityType }) + + UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique }); + const inlineEditPath = UMB_EDIT_DOCUMENT_TYPE_FOLDER_WORKSPACE_PATH_PATTERN.generateAbsolute({ + unique: item.unique, + }); + + return { + id: item.unique, + icon: item.isFolder && !item.icon ? 'icon-folder' : item.icon, + data: [ + { + columnAlias: 'name', + value: html``, + }, + { + columnAlias: 'isElementType', + value: html``, + }, + { + columnAlias: 'entityActions', + value: html``, + }, + ], + }; + }); + } + + override render() { + return html` + + `; + } + + static override styles = [ + UmbTextStyles, + css` + :host { + display: flex; + flex-direction: column; + } + `, + ]; +} + +export { UmbDocumentTypeTreeItemTableCollectionViewElement as element }; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbDocumentTypeTreeItemTableCollectionViewElement; + } +} diff --git a/src/packages/documents/document-types/tree/tree-item-children/collection/views/manifests.ts b/src/packages/documents/document-types/tree/tree-item-children/collection/views/manifests.ts new file mode 100644 index 0000000000..489068637d --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/collection/views/manifests.ts @@ -0,0 +1,23 @@ +import { UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_ALIAS } from '../constants.js'; +import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection'; + +export const manifests: Array = [ + { + type: 'collectionView', + alias: 'Umb.CollectionView.DocumentType.TreeItem.Table', + name: 'Document Type Tree Item Table Collection View', + element: () => import('./document-type-tree-item-table-collection-view.element.js'), + weight: 300, + meta: { + label: 'Table', + icon: 'icon-list', + pathName: 'table', + }, + conditions: [ + { + alias: UMB_COLLECTION_ALIAS_CONDITION, + match: UMB_DOCUMENT_TYPE_TREE_ITEM_CHILDREN_COLLECTION_ALIAS, + }, + ], + }, +]; diff --git a/src/packages/documents/document-types/tree/tree-item-children/manifests.ts b/src/packages/documents/document-types/tree/tree-item-children/manifests.ts new file mode 100644 index 0000000000..b6ea5ab54e --- /dev/null +++ b/src/packages/documents/document-types/tree/tree-item-children/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as collectionManifests } from './collection/manifests.js'; +import { manifests as reloadEntityActionManifests } from './reload-entity-action/manifests.js'; + +export const manifests: Array = [...collectionManifests, ...reloadEntityActionManifests]; diff --git a/src/packages/documents/document-types/tree/reload-tree-item-children/manifests.ts b/src/packages/documents/document-types/tree/tree-item-children/reload-entity-action/manifests.ts similarity index 68% rename from src/packages/documents/document-types/tree/reload-tree-item-children/manifests.ts rename to src/packages/documents/document-types/tree/tree-item-children/reload-entity-action/manifests.ts index 550364e51b..8f0ada4ebc 100644 --- a/src/packages/documents/document-types/tree/reload-tree-item-children/manifests.ts +++ b/src/packages/documents/document-types/tree/tree-item-children/reload-entity-action/manifests.ts @@ -1,8 +1,5 @@ -import { - UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, - UMB_DOCUMENT_TYPE_ENTITY_TYPE, - UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE, -} from '../../entity.js'; +import { UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../../entity.js'; +import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../folder/index.js'; export const manifests: Array = [ { diff --git a/src/packages/documents/document-types/tree/types.ts b/src/packages/documents/document-types/tree/types.ts index 24d72d14ff..c8eeff4228 100644 --- a/src/packages/documents/document-types/tree/types.ts +++ b/src/packages/documents/document-types/tree/types.ts @@ -1,8 +1,5 @@ -import type { - UmbDocumentTypeEntityType, - UmbDocumentTypeFolderEntityType, - UmbDocumentTypeRootEntityType, -} from '../entity.js'; +import type { UmbDocumentTypeEntityType, UmbDocumentTypeRootEntityType } from '../entity.js'; +import type { UmbDocumentTypeFolderEntityType } from './folder/index.js'; import type { UmbTreeItemModel, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree'; export interface UmbDocumentTypeTreeItemModel extends UmbTreeItemModel { diff --git a/src/packages/documents/document-types/workspace/document-type-root/constants.ts b/src/packages/documents/document-types/workspace/document-type-root/constants.ts new file mode 100644 index 0000000000..494ee8fb32 --- /dev/null +++ b/src/packages/documents/document-types/workspace/document-type-root/constants.ts @@ -0,0 +1 @@ +export const UMB_DOCUMENT_TYPE_ROOT_WORKSPACE_ALIAS = 'Umb.Workspace.DocumentType.Root'; diff --git a/src/packages/documents/document-types/workspace/document-type-root/index.ts b/src/packages/documents/document-types/workspace/document-type-root/index.ts new file mode 100644 index 0000000000..4f07201dcf --- /dev/null +++ b/src/packages/documents/document-types/workspace/document-type-root/index.ts @@ -0,0 +1 @@ +export * from './constants.js'; diff --git a/src/packages/documents/document-types/workspace/document-type-root/manifests.ts b/src/packages/documents/document-types/workspace/document-type-root/manifests.ts new file mode 100644 index 0000000000..c61b55778a --- /dev/null +++ b/src/packages/documents/document-types/workspace/document-type-root/manifests.ts @@ -0,0 +1,14 @@ +import { UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from '../../entity.js'; + +export const manifests: Array = [ + { + type: 'workspace', + kind: 'default', + alias: 'Umb.Workspace.DocumentType.Root', + name: 'Document Type Root Workspace', + meta: { + entityType: UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, + headline: '#treeHeaders_documentTypes', + }, + }, +]; diff --git a/src/packages/documents/document-types/workspace/document-type/constants.ts b/src/packages/documents/document-types/workspace/document-type/constants.ts new file mode 100644 index 0000000000..6592bb27dd --- /dev/null +++ b/src/packages/documents/document-types/workspace/document-type/constants.ts @@ -0,0 +1 @@ +export const UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.DocumentType'; diff --git a/src/packages/documents/document-types/workspace/document-type-workspace-editor.element.ts b/src/packages/documents/document-types/workspace/document-type/document-type-workspace-editor.element.ts similarity index 100% rename from src/packages/documents/document-types/workspace/document-type-workspace-editor.element.ts rename to src/packages/documents/document-types/workspace/document-type/document-type-workspace-editor.element.ts diff --git a/src/packages/documents/document-types/workspace/document-type-workspace.context-token.ts b/src/packages/documents/document-types/workspace/document-type/document-type-workspace.context-token.ts similarity index 100% rename from src/packages/documents/document-types/workspace/document-type-workspace.context-token.ts rename to src/packages/documents/document-types/workspace/document-type/document-type-workspace.context-token.ts diff --git a/src/packages/documents/document-types/workspace/document-type-workspace.context.ts b/src/packages/documents/document-types/workspace/document-type/document-type-workspace.context.ts similarity index 96% rename from src/packages/documents/document-types/workspace/document-type-workspace.context.ts rename to src/packages/documents/document-types/workspace/document-type/document-type-workspace.context.ts index e1791d7ec0..888a9a30e9 100644 --- a/src/packages/documents/document-types/workspace/document-type-workspace.context.ts +++ b/src/packages/documents/document-types/workspace/document-type/document-type-workspace.context.ts @@ -4,10 +4,10 @@ import { UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_TEMPLATE, UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN, type UmbCreateDocumentTypeWorkspacePresetType, -} from '../paths.js'; -import type { UmbDocumentTypeDetailModel } from '../types.js'; -import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../entity.js'; -import { UmbDocumentTypeDetailRepository } from '../repository/detail/document-type-detail.repository.js'; +} from '../../paths.js'; +import type { UmbDocumentTypeDetailModel } from '../../types.js'; +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../entity.js'; +import { UmbDocumentTypeDetailRepository } from '../../repository/detail/document-type-detail.repository.js'; import { UmbDocumentTypeWorkspaceEditorElement } from './document-type-workspace-editor.element.js'; import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; @@ -110,7 +110,7 @@ export class UmbDocumentTypeWorkspaceContext { path: UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.toString(), component: UmbDocumentTypeWorkspaceEditorElement, - setup: (_component, info) => { + setup: async (_component, info) => { const params = info.match.params as unknown as UmbPathPatternTypeAsEncodedParamsType< typeof UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.PARAMS >; @@ -120,7 +120,7 @@ export class UmbDocumentTypeWorkspaceContext if (parentUnique === undefined) { throw new Error('ParentUnique url parameter is required to create a document type'); } - this.create({ entityType: parentEntityType, unique: parentUnique }, presetAlias); + await this.create({ entityType: parentEntityType, unique: parentUnique }, presetAlias); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/documents/document-types/workspace/document-type-workspace.modal-token.ts b/src/packages/documents/document-types/workspace/document-type/document-type-workspace.modal-token.ts similarity index 93% rename from src/packages/documents/document-types/workspace/document-type-workspace.modal-token.ts rename to src/packages/documents/document-types/workspace/document-type/document-type-workspace.modal-token.ts index 46162c38e0..e30cbcfd61 100644 --- a/src/packages/documents/document-types/workspace/document-type-workspace.modal-token.ts +++ b/src/packages/documents/document-types/workspace/document-type/document-type-workspace.modal-token.ts @@ -1,4 +1,4 @@ -import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../entity.js'; +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../entity.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils'; diff --git a/src/packages/documents/document-types/workspace/document-type/index.ts b/src/packages/documents/document-types/workspace/document-type/index.ts new file mode 100644 index 0000000000..5232133b80 --- /dev/null +++ b/src/packages/documents/document-types/workspace/document-type/index.ts @@ -0,0 +1,3 @@ +export * from './document-type-workspace.context-token.js'; +export * from './document-type-workspace.modal-token.js'; +export * from './constants.js'; diff --git a/src/packages/documents/document-types/workspace/document-type/manifests.ts b/src/packages/documents/document-types/workspace/document-type/manifests.ts new file mode 100644 index 0000000000..b4644169cb --- /dev/null +++ b/src/packages/documents/document-types/workspace/document-type/manifests.ts @@ -0,0 +1,106 @@ +import { UMB_DOCUMENT_TYPE_COMPOSITION_REPOSITORY_ALIAS } from '../../repository/composition/index.js'; +import { UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; + +export const manifests: Array = [ + { + type: 'workspace', + kind: 'routable', + alias: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, + name: 'Document Type Workspace', + api: () => import('./document-type-workspace.context.js'), + meta: { + entityType: 'document-type', + }, + }, + { + type: 'workspaceView', + kind: 'contentTypeDesignEditor', + alias: 'Umb.WorkspaceView.DocumentType.Design', + name: 'Document Type Workspace Design View', + meta: { + label: '#general_design', + pathname: 'design', + icon: 'icon-document-dashed-line', + compositionRepositoryAlias: UMB_DOCUMENT_TYPE_COMPOSITION_REPOSITORY_ALIAS, + }, + conditions: [ + { + alias: UMB_WORKSPACE_CONDITION_ALIAS, + match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, + }, + ], + }, + { + type: 'workspaceView', + alias: 'Umb.WorkspaceView.DocumentType.Structure', + name: 'Document Type Workspace Structure View', + element: () => import('./views/structure/document-type-workspace-view-structure.element.js'), + weight: 800, + meta: { + label: '#contentTypeEditor_structure', + pathname: 'structure', + icon: 'icon-mindmap', + }, + conditions: [ + { + alias: UMB_WORKSPACE_CONDITION_ALIAS, + match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, + }, + ], + }, + { + type: 'workspaceView', + alias: 'Umb.WorkspaceView.DocumentType.Settings', + name: 'Document Type Workspace Settings View', + element: () => import('./views/settings/document-type-workspace-view-settings.element.js'), + weight: 600, + meta: { + label: '#general_settings', + pathname: 'settings', + icon: 'icon-settings', + }, + conditions: [ + { + alias: UMB_WORKSPACE_CONDITION_ALIAS, + match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, + }, + ], + }, + { + type: 'workspaceView', + alias: 'Umb.WorkspaceView.DocumentType.Templates', + name: 'Document Type Workspace Templates View', + element: () => import('./views/templates/document-type-workspace-view-templates.element.js'), + weight: 400, + meta: { + label: '#treeHeaders_templates', + pathname: 'templates', + icon: 'icon-layout', + }, + conditions: [ + { + alias: UMB_WORKSPACE_CONDITION_ALIAS, + match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, + }, + ], + }, + { + type: 'workspaceAction', + kind: 'default', + alias: 'Umb.WorkspaceAction.DocumentType.Save', + name: 'Save Document Type Workspace Action', + api: UmbSubmitWorkspaceAction, + meta: { + label: '#buttons_save', + look: 'primary', + color: 'positive', + }, + conditions: [ + { + alias: UMB_WORKSPACE_CONDITION_ALIAS, + match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, + }, + ], + }, +]; diff --git a/src/packages/documents/document-types/workspace/views/settings/document-type-workspace-view-settings.element.ts b/src/packages/documents/document-types/workspace/document-type/views/settings/document-type-workspace-view-settings.element.ts similarity index 100% rename from src/packages/documents/document-types/workspace/views/settings/document-type-workspace-view-settings.element.ts rename to src/packages/documents/document-types/workspace/document-type/views/settings/document-type-workspace-view-settings.element.ts diff --git a/src/packages/documents/document-types/workspace/views/structure/document-type-workspace-view-structure.element.ts b/src/packages/documents/document-types/workspace/document-type/views/structure/document-type-workspace-view-structure.element.ts similarity index 97% rename from src/packages/documents/document-types/workspace/views/structure/document-type-workspace-view-structure.element.ts rename to src/packages/documents/document-types/workspace/document-type/views/structure/document-type-workspace-view-structure.element.ts index 0a490ea644..5edd8d234d 100644 --- a/src/packages/documents/document-types/workspace/views/structure/document-type-workspace-view-structure.element.ts +++ b/src/packages/documents/document-types/workspace/document-type/views/structure/document-type-workspace-view-structure.element.ts @@ -1,5 +1,5 @@ import type { UmbDocumentTypeWorkspaceContext } from '../../document-type-workspace.context.js'; -import type { UmbInputDocumentTypeElement } from '../../../components/input-document-type/input-document-type.element.js'; +import type { UmbInputDocumentTypeElement } from '../../../../components/input-document-type/input-document-type.element.js'; import { UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT } from '../../document-type-workspace.context-token.js'; import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; diff --git a/src/packages/documents/document-types/workspace/views/templates/document-type-workspace-view-templates.element.ts b/src/packages/documents/document-types/workspace/document-type/views/templates/document-type-workspace-view-templates.element.ts similarity index 100% rename from src/packages/documents/document-types/workspace/views/templates/document-type-workspace-view-templates.element.ts rename to src/packages/documents/document-types/workspace/document-type/views/templates/document-type-workspace-view-templates.element.ts diff --git a/src/packages/documents/document-types/workspace/index.ts b/src/packages/documents/document-types/workspace/index.ts index cf7ab5393f..7aa5441487 100644 --- a/src/packages/documents/document-types/workspace/index.ts +++ b/src/packages/documents/document-types/workspace/index.ts @@ -1,2 +1 @@ -export * from './document-type-workspace.context-token.js'; -export * from './document-type-workspace.modal-token.js'; +export * from './document-type/index.js'; diff --git a/src/packages/documents/document-types/workspace/manifests.ts b/src/packages/documents/document-types/workspace/manifests.ts index 028faf033a..8d1f79b665 100644 --- a/src/packages/documents/document-types/workspace/manifests.ts +++ b/src/packages/documents/document-types/workspace/manifests.ts @@ -1,108 +1,4 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; -import { UMB_DOCUMENT_TYPE_COMPOSITION_REPOSITORY_ALIAS } from '../repository/composition/index.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { manifests as documentTypeManifests } from './document-type/manifests.js'; +import { manifests as documentTypeRootManifests } from './document-type-root/manifests.js'; -export const UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.DocumentType'; - -export const manifests: Array = [ - { - type: 'workspace', - kind: 'routable', - alias: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, - name: 'Document Type Workspace', - api: () => import('./document-type-workspace.context.js'), - meta: { - entityType: 'document-type', - }, - }, - { - type: 'workspaceView', - kind: 'contentTypeDesignEditor', - alias: 'Umb.WorkspaceView.DocumentType.Design', - name: 'Document Type Workspace Design View', - meta: { - label: '#general_design', - pathname: 'design', - icon: 'icon-document-dashed-line', - compositionRepositoryAlias: UMB_DOCUMENT_TYPE_COMPOSITION_REPOSITORY_ALIAS, - }, - conditions: [ - { - alias: UMB_WORKSPACE_CONDITION_ALIAS, - match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, - }, - ], - }, - { - type: 'workspaceView', - alias: 'Umb.WorkspaceView.DocumentType.Structure', - name: 'Document Type Workspace Structure View', - element: () => import('./views/structure/document-type-workspace-view-structure.element.js'), - weight: 800, - meta: { - label: '#contentTypeEditor_structure', - pathname: 'structure', - icon: 'icon-mindmap', - }, - conditions: [ - { - alias: UMB_WORKSPACE_CONDITION_ALIAS, - match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, - }, - ], - }, - { - type: 'workspaceView', - alias: 'Umb.WorkspaceView.DocumentType.Settings', - name: 'Document Type Workspace Settings View', - element: () => import('./views/settings/document-type-workspace-view-settings.element.js'), - weight: 600, - meta: { - label: '#general_settings', - pathname: 'settings', - icon: 'icon-settings', - }, - conditions: [ - { - alias: UMB_WORKSPACE_CONDITION_ALIAS, - match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, - }, - ], - }, - { - type: 'workspaceView', - alias: 'Umb.WorkspaceView.DocumentType.Templates', - name: 'Document Type Workspace Templates View', - element: () => import('./views/templates/document-type-workspace-view-templates.element.js'), - weight: 400, - meta: { - label: '#treeHeaders_templates', - pathname: 'templates', - icon: 'icon-layout', - }, - conditions: [ - { - alias: UMB_WORKSPACE_CONDITION_ALIAS, - match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, - }, - ], - }, - { - type: 'workspaceAction', - kind: 'default', - alias: 'Umb.WorkspaceAction.DocumentType.Save', - name: 'Save Document Type Workspace Action', - api: UmbSubmitWorkspaceAction, - meta: { - label: '#buttons_save', - look: 'primary', - color: 'positive', - }, - conditions: [ - { - alias: UMB_WORKSPACE_CONDITION_ALIAS, - match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, - }, - ], - }, -]; +export const manifests: Array = [...documentTypeManifests, ...documentTypeRootManifests]; diff --git a/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts b/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts index 29e72c693b..4b0fafd6f8 100644 --- a/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts +++ b/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts @@ -12,6 +12,7 @@ import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import type { ManifestCollectionAction } from '@umbraco-cms/backoffice/collection'; import type { UmbAllowedDocumentTypeModel } from '@umbraco-cms/backoffice/document-type'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; @customElement('umb-create-document-collection-action') export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { @@ -25,7 +26,7 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { private _currentView?: string; @state() - private _documentUnique?: string; + private _documentUnique?: UmbEntityUnique; @state() private _documentTypeUnique?: string; diff --git a/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts b/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts index a4e4d89e2f..c69efbe228 100644 --- a/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts +++ b/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts @@ -136,7 +136,7 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { }; }); - this._tableColumns = [...this.#systemColumns, ...userColumns]; + this._tableColumns = [...this.#systemColumns, ...userColumns, { name: '', alias: 'entityActions' }]; } } @@ -146,6 +146,17 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { const data = this._tableColumns?.map((column) => { + if (column.alias === 'entityActions') { + return { + columnAlias: 'entityActions', + value: html``, + }; + } + const editPath = this.#routeBuilder ? this.#routeBuilder({ entityType: item.entityType }) + UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique }) diff --git a/src/packages/documents/documents/entity-actions/public-access/repository/public-access.repository.ts b/src/packages/documents/documents/entity-actions/public-access/repository/public-access.repository.ts index df0b349e33..c6799310be 100644 --- a/src/packages/documents/documents/entity-actions/public-access/repository/public-access.repository.ts +++ b/src/packages/documents/documents/entity-actions/public-access/repository/public-access.repository.ts @@ -27,7 +27,7 @@ export class UmbDocumentPublicAccessRepository extends UmbControllerBase impleme const { error } = await this.#dataSource.create(unique, data); if (!error) { - const notification = { data: { message: `Public acccess setting created` } }; + const notification = { data: { message: `Public access setting created` } }; this.#notificationContext?.peek('positive', notification); } return { error }; @@ -46,7 +46,7 @@ export class UmbDocumentPublicAccessRepository extends UmbControllerBase impleme const { error } = await this.#dataSource.update(unique, data); if (!error) { - const notification = { data: { message: `Public acccess setting updated` } }; + const notification = { data: { message: `Public access setting updated` } }; this.#notificationContext?.peek('positive', notification); } return { error }; @@ -57,7 +57,7 @@ export class UmbDocumentPublicAccessRepository extends UmbControllerBase impleme const { error } = await this.#dataSource.delete(unique); if (!error) { - const notification = { data: { message: `Public acccess setting deleted` } }; + const notification = { data: { message: `Public access setting deleted` } }; this.#notificationContext?.peek('positive', notification); } return { error }; diff --git a/src/packages/documents/documents/entity-actions/publish.action.ts b/src/packages/documents/documents/entity-actions/publish.action.ts index 1175fd3361..845f47e73d 100644 --- a/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/packages/documents/documents/entity-actions/publish.action.ts @@ -8,6 +8,7 @@ import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user'; export class UmbPublishDocumentEntityAction extends UmbEntityActionBase { constructor(host: UmbControllerHost, args: UmbEntityActionArgs) { @@ -25,8 +26,16 @@ export class UmbPublishDocumentEntityAction extends UmbEntityActionBase { if (!documentData) throw new Error('The document was not found'); - const context = await this.getContext(UMB_APP_LANGUAGE_CONTEXT); - const appCulture = context.getAppCulture(); + const appLanguageContext = await this.getContext(UMB_APP_LANGUAGE_CONTEXT); + const appCulture = appLanguageContext.getAppCulture(); + + const currentUserContext = await this.getContext(UMB_CURRENT_USER_CONTEXT); + const currentUserAllowedLanguages = currentUserContext.getLanguages(); + const currentUserHasAccessToAllLanguages = currentUserContext.getHasAccessToAllLanguages(); + + if (currentUserAllowedLanguages === undefined) throw new Error('The current user languages are missing'); + if (currentUserHasAccessToAllLanguages === undefined) + throw new Error('The current user access to all languages is missing'); const options: Array = documentData.variants.map( (variant) => ({ @@ -76,6 +85,11 @@ export class UmbPublishDocumentEntityAction extends UmbEntityActionBase { .open(this, UMB_DOCUMENT_PUBLISH_MODAL, { data: { options, + pickableFilter: (option) => { + if (!option.culture) return false; + if (currentUserHasAccessToAllLanguages) return true; + return currentUserAllowedLanguages.includes(option.culture); + }, }, value: { selection }, }) diff --git a/src/packages/documents/documents/entity-actions/unpublish.action.ts b/src/packages/documents/documents/entity-actions/unpublish.action.ts index 46d3471ea3..a885966c88 100644 --- a/src/packages/documents/documents/entity-actions/unpublish.action.ts +++ b/src/packages/documents/documents/entity-actions/unpublish.action.ts @@ -11,6 +11,7 @@ import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user'; export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase { constructor(host: UmbControllerHost, args: UmbEntityActionArgs) { @@ -28,8 +29,16 @@ export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase if (!documentData) throw new Error('The document was not found'); - const context = await this.getContext(UMB_APP_LANGUAGE_CONTEXT); - const appCulture = context.getAppCulture(); + const appLanguageContext = await this.getContext(UMB_APP_LANGUAGE_CONTEXT); + const appCulture = appLanguageContext.getAppCulture(); + + const currentUserContext = await this.getContext(UMB_CURRENT_USER_CONTEXT); + const currentUserAllowedLanguages = currentUserContext.getLanguages(); + const currentUserHasAccessToAllLanguages = currentUserContext.getHasAccessToAllLanguages(); + + if (currentUserAllowedLanguages === undefined) throw new Error('The current user languages are missing'); + if (currentUserHasAccessToAllLanguages === undefined) + throw new Error('The current user access to all languages is missing'); const options: Array = documentData.variants.map( (variant) => ({ @@ -65,6 +74,11 @@ export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase data: { documentUnique: this.args.unique, options, + pickableFilter: (option) => { + if (!option.culture) return false; + if (currentUserHasAccessToAllLanguages) return true; + return currentUserAllowedLanguages.includes(option.culture); + }, }, value: { selection }, }) @@ -91,4 +105,5 @@ export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase } } } + export default UmbUnpublishDocumentEntityAction; diff --git a/src/packages/documents/documents/menu/manifests.ts b/src/packages/documents/documents/menu/manifests.ts index a842fa686c..dc76417093 100644 --- a/src/packages/documents/documents/menu/manifests.ts +++ b/src/packages/documents/documents/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_CONTENT_MENU_ALIAS = 'Umb.Menu.Content'; diff --git a/src/packages/documents/documents/modals/types.ts b/src/packages/documents/documents/modals/types.ts index d3e91cdf89..56eeb92f50 100644 --- a/src/packages/documents/documents/modals/types.ts +++ b/src/packages/documents/documents/modals/types.ts @@ -1,10 +1,5 @@ import type { UmbDocumentVariantOptionModel } from '../types.js'; +import type { UmbContentVariantPickerData, UmbContentVariantPickerValue } from '@umbraco-cms/backoffice/content'; -export interface UmbDocumentVariantPickerData { - options: Array; - pickableFilter?: (variantOption: UmbDocumentVariantOptionModel) => boolean; -} - -export interface UmbDocumentVariantPickerValue { - selection: Array; -} +export type UmbDocumentVariantPickerData = UmbContentVariantPickerData; +export type UmbDocumentVariantPickerValue = UmbContentVariantPickerValue; diff --git a/src/packages/documents/documents/rollback/entity-action/manifests.ts b/src/packages/documents/documents/rollback/entity-action/manifests.ts index 2bed9b0321..4e7d45616a 100644 --- a/src/packages/documents/documents/rollback/entity-action/manifests.ts +++ b/src/packages/documents/documents/rollback/entity-action/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; import { UMB_USER_PERMISSION_DOCUMENT_ROLLBACK } from '../../user-permissions/index.js'; import { UMB_DOCUMENT_WORKSPACE_ALIAS } from '../../workspace/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; export const manifests: Array = [ diff --git a/src/packages/documents/documents/workspace/document-workspace-split-view-variant-selector.element.ts b/src/packages/documents/documents/workspace/document-workspace-split-view-variant-selector.element.ts new file mode 100644 index 0000000000..f08abb5f61 --- /dev/null +++ b/src/packages/documents/documents/workspace/document-workspace-split-view-variant-selector.element.ts @@ -0,0 +1,30 @@ +import type { UmbDocumentVariantOptionModel } from '../types.js'; +import { sortVariants } from '../utils.js'; +import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { UmbWorkspaceSplitViewVariantSelectorElement } from '@umbraco-cms/backoffice/workspace'; + +const elementName = 'umb-document-workspace-split-view-variant-selector'; +@customElement(elementName) +export class UmbDocumentWorkspaceSplitViewVariantSelectorElement extends UmbWorkspaceSplitViewVariantSelectorElement { + protected override _variantSorter = sortVariants; + + #publishStateLocalizationMap = { + [DocumentVariantStateModel.DRAFT]: 'content_unpublished', + [DocumentVariantStateModel.PUBLISHED]: 'content_published', + [DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES]: 'content_publishedPendingChanges', + [DocumentVariantStateModel.NOT_CREATED]: 'content_notCreated', + }; + + override _renderVariantDetails(variantOption: UmbDocumentVariantOptionModel) { + return html` ${this.localize.term( + this.#publishStateLocalizationMap[variantOption.variant?.state || DocumentVariantStateModel.NOT_CREATED], + )}`; + } +} + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbDocumentWorkspaceSplitViewVariantSelectorElement; + } +} diff --git a/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts b/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts index 97f56208fa..09c5ab3bbc 100644 --- a/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts +++ b/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts @@ -4,6 +4,8 @@ import { css, html, nothing, customElement, state, repeat } from '@umbraco-cms/b import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import './document-workspace-split-view-variant-selector.element.js'; + @customElement('umb-document-workspace-split-view') export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement { // TODO: Refactor: use the split view context token: @@ -15,7 +17,7 @@ export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement { constructor() { super(); - // TODO: Refactor: use a split view workspace context token: + // TODO: Refactor: use a split view workspace context token: [NL] this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => { this._workspaceContext = context; this._observeActiveVariantInfo(); @@ -44,7 +46,10 @@ export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement { + .displayNavigation=${view.index === this._variants!.length - 1}> + + `, )} diff --git a/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/packages/documents/documents/workspace/document-workspace.context.ts index 4c21724833..b9ef2f99dc 100644 --- a/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -1,19 +1,14 @@ import { UmbDocumentTypeDetailRepository } from '../../document-types/repository/detail/document-type-detail.repository.js'; import { UmbDocumentPropertyDatasetContext } from '../property-dataset-context/document-property-dataset-context.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; -import { UmbDocumentDetailRepository } from '../repository/index.js'; -import type { - UmbDocumentVariantPublishModel, - UmbDocumentDetailModel, - UmbDocumentValueModel, - UmbDocumentVariantModel, - UmbDocumentVariantOptionModel, -} from '../types.js'; +import type { UmbDocumentDetailRepository } from '../repository/index.js'; +import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; +import type { UmbDocumentVariantPublishModel, UmbDocumentDetailModel, UmbDocumentVariantModel } from '../types.js'; import { UMB_DOCUMENT_PUBLISH_MODAL, UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL, - UMB_DOCUMENT_SCHEDULE_MODAL, UMB_DOCUMENT_SAVE_MODAL, + UMB_DOCUMENT_SCHEDULE_MODAL, } from '../modals/index.js'; import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js'; import { UmbUnpublishDocumentEntityAction } from '../entity-actions/unpublish.action.js'; @@ -23,224 +18,76 @@ import { UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN, UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN, } from '../paths.js'; -import { UMB_DOCUMENTS_SECTION_PATH } from '../../section/paths.js'; import { UmbDocumentPreviewRepository } from '../repository/preview/index.js'; -import { sortVariants } from '../utils.js'; import { UMB_DOCUMENT_COLLECTION_ALIAS } from '../collection/index.js'; import { UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD, UMB_DOCUMENT_WORKSPACE_ALIAS } from './constants.js'; -import { UmbEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; import { type UmbPublishableWorkspaceContext, - UmbSubmittableWorkspaceContextBase, UmbWorkspaceIsNewRedirectController, - UmbWorkspaceSplitViewManager, UmbWorkspaceIsNewRedirectControllerAlias, } from '@umbraco-cms/backoffice/workspace'; -import { - appendToFrozenArray, - mergeObservables, - UmbArrayState, - UmbObjectState, -} from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import { type Observable, firstValueFrom, map } from '@umbraco-cms/backoffice/external/rxjs'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; import { UmbRequestReloadChildrenOfEntityEvent, UmbRequestReloadStructureForEntityEvent, } from '@umbraco-cms/backoffice/entity-action'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import { - UMB_VALIDATION_CONTEXT, - UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, - UmbDataPathVariantQuery, - UmbServerModelValidatorContext, - UmbValidationContext, - UmbVariantValuesValidationPathTranslator, - UmbVariantsValidationPathTranslator, -} from '@umbraco-cms/backoffice/validation'; +import { UmbServerModelValidatorContext } from '@umbraco-cms/backoffice/validation'; import { UmbDocumentBlueprintDetailRepository } from '@umbraco-cms/backoffice/document-blueprint'; import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; import { - UmbContentWorkspaceDataManager, - UmbMergeContentVariantDataController, + UmbContentDetailWorkspaceContextBase, type UmbContentCollectionWorkspaceContext, type UmbContentWorkspaceContext, } from '@umbraco-cms/backoffice/content'; import type { UmbDocumentTypeDetailModel } from '@umbraco-cms/backoffice/document-type'; import { UmbIsTrashedEntityContext } from '@umbraco-cms/backoffice/recycle-bin'; -import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; -import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; -import type { UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository'; import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; -type EntityModel = UmbDocumentDetailModel; -type EntityTypeModel = UmbDocumentTypeDetailModel; +type ContentModel = UmbDocumentDetailModel; +type ContentTypeModel = UmbDocumentTypeDetailModel; export class UmbDocumentWorkspaceContext - extends UmbSubmittableWorkspaceContextBase + extends UmbContentDetailWorkspaceContextBase< + ContentModel, + UmbDocumentDetailRepository, + ContentTypeModel, + UmbDocumentVariantModel + > implements - UmbContentWorkspaceContext, + UmbContentWorkspaceContext, UmbPublishableWorkspaceContext, UmbContentCollectionWorkspaceContext { - public readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const; - - public readonly repository = new UmbDocumentDetailRepository(this); public readonly publishingRepository = new UmbDocumentPublishingRepository(this); - #parent = new UmbObjectState(undefined); - readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined)); - readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined)); - - readonly #data = new UmbContentWorkspaceDataManager(this, UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD); - #getDataPromise?: Promise>; - - // TODo: Optimize this so it uses either a App Language Context? [NL] - #languageRepository = new UmbLanguageCollectionRepository(this); - #languages = new UmbArrayState([], (x) => x.unique); - /** - * @private - * @description - Should not be used by external code. - */ - public readonly languages = this.#languages.asObservable(); - #serverValidation = new UmbServerModelValidatorContext(this); #validationRepository?: UmbDocumentValidationRepository; - public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); - - public isLoaded() { - return this.#getDataPromise; - } - - readonly data = this.#data.current; - readonly unique = this.#data.createObservablePartOfCurrent((data) => data?.unique); - readonly entityType = this.#data.createObservablePartOfCurrent((data) => data?.entityType); - readonly isTrashed = this.#data.createObservablePartOfCurrent((data) => data?.isTrashed); - readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); - getValues() { - return this.#data.getCurrent()?.values; - } - - readonly contentTypeUnique = this.#data.createObservablePartOfCurrent((data) => data?.documentType.unique); - readonly contentTypeHasCollection = this.#data.createObservablePartOfCurrent( + readonly isTrashed = this._data.createObservablePartOfCurrent((data) => data?.isTrashed); + readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.documentType.unique); + readonly contentTypeHasCollection = this._data.createObservablePartOfCurrent( (data) => !!data?.documentType.collection, ); + readonly urls = this._data.createObservablePartOfCurrent((data) => data?.urls || []); + readonly templateId = this._data.createObservablePartOfCurrent((data) => data?.template?.unique || null); - readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants ?? []); - - readonly structure = new UmbContentTypeStructureManager(this, new UmbDocumentTypeDetailRepository(this)); - readonly variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); - readonly variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); - readonly varies = this.structure.ownerContentTypeObservablePart((x) => - x ? x.variesByCulture || x.variesBySegment : undefined, - ); - #varies?: boolean; - #variesByCulture?: boolean; - #variesBySegment?: boolean; - - readonly urls = this.#data.createObservablePartOfCurrent((data) => data?.urls || []); - readonly templateId = this.#data.createObservablePartOfCurrent((data) => data?.template?.unique || null); - - readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); - #dataTypeSchemaAliasMap = new Map(); - - readonly splitView = new UmbWorkspaceSplitViewManager(); - - readonly variantOptions = mergeObservables( - [this.varies, this.variants, this.languages], - ([varies, variants, languages]) => { - // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL] - if (varies === true) { - return languages.map((language) => { - return { - variant: variants.find((x) => x.culture === language.unique), - language, - // TODO: When including segments, this object should be updated to include a object for the segment. [NL] - // TODO: When including segments, the unique should be updated to include the segment as well. [NL] - unique: language.unique, // This must be a variantId string! - culture: language.unique, - segment: null, - } as UmbDocumentVariantOptionModel; - }); - } else if (varies === false) { - return [ - { - variant: variants.find((x) => x.culture === null), - language: languages.find((x) => x.isDefault), - culture: null, - segment: null, - unique: UMB_INVARIANT_CULTURE, // This must be a variantId string! - } as UmbDocumentVariantOptionModel, - ]; - } - return [] as Array; - }, - ).pipe(map((results) => results.sort(sortVariants))); - - // TODO: this should be set up for all entity workspace contexts in a base class - #entityContext = new UmbEntityContext(this); - // TODO: this might not be the correct place to spin this up #isTrashedContext = new UmbIsTrashedEntityContext(this); constructor(host: UmbControllerHost) { - super(host, UMB_DOCUMENT_WORKSPACE_ALIAS); - - this.addValidationContext(new UmbValidationContext(this)); - - new UmbVariantValuesValidationPathTranslator(this); - new UmbVariantsValidationPathTranslator(this); + super(host, { + entityType: UMB_DOCUMENT_ENTITY_TYPE, + workspaceAlias: UMB_DOCUMENT_WORKSPACE_ALIAS, + detailRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, + contentTypeDetailRepository: UmbDocumentTypeDetailRepository, + contentVariantScaffold: UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD, + saveModalToken: UMB_DOCUMENT_SAVE_MODAL, + }); this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); - this.observe( - this.varies, - (varies) => { - this.#data.setVaries(varies); - this.#varies = varies; - }, - null, - ); - this.observe( - this.variesByCulture, - (varies) => { - this.#data.setVariesByCulture(varies); - this.#variesByCulture = varies; - }, - null, - ); - this.observe( - this.variesBySegment, - (varies) => { - this.#data.setVariesBySegment(varies); - this.#variesBySegment = varies; - }, - null, - ); - this.observe( - this.structure.contentTypeDataTypeUniques, - (dataTypeUniques: Array) => { - this.#dataTypeItemManager.setUniques(dataTypeUniques); - }, - null, - ); - this.observe( - this.#dataTypeItemManager.items, - (dataTypes) => { - // Make a map of the data type unique and editorAlias: - this.#dataTypeSchemaAliasMap = new Map( - dataTypes.map((dataType) => { - return [dataType.unique, dataType.propertyEditorSchemaAlias]; - }), - ); - }, - null, - ); - - this.loadLanguages(); this.routes.setRoutes([ { @@ -253,7 +100,11 @@ export class UmbDocumentWorkspaceContext const documentTypeUnique = info.match.params.documentTypeUnique; const blueprintUnique = info.match.params.blueprintUnique; - this.create({ entityType: parentEntityType, unique: parentUnique }, documentTypeUnique, blueprintUnique); + await this.create( + { entityType: parentEntityType, unique: parentUnique }, + documentTypeUnique, + blueprintUnique, + ); new UmbWorkspaceIsNewRedirectController( this, this, @@ -268,7 +119,7 @@ export class UmbDocumentWorkspaceContext const parentEntityType = info.match.params.parentEntityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; const documentTypeUnique = info.match.params.documentTypeUnique; - this.create({ entityType: parentEntityType, unique: parentUnique }, documentTypeUnique); + await this.create({ entityType: parentEntityType, unique: parentUnique }, documentTypeUnique); new UmbWorkspaceIsNewRedirectController( this, @@ -289,303 +140,74 @@ export class UmbDocumentWorkspaceContext ]); } - override resetState() { - super.resetState(); - this.#data.clear(); - this.removeUmbControllerByAlias(UmbWorkspaceIsNewRedirectControllerAlias); - } - - async loadLanguages() { - // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] - const { data } = await this.#languageRepository.requestCollection({}); - this.#languages.setValue(data?.items ?? []); - } - - async load(unique: string) { - this.resetState(); - this.#getDataPromise = this.repository.requestByUnique(unique); + override async load(unique: string) { + const response = await super.load(unique); - type GetDataType = Awaited>; - const { data, asObservable } = (await this.#getDataPromise) as GetDataType; - - if (data) { - this.#entityContext.setEntityType(UMB_DOCUMENT_ENTITY_TYPE); - this.#entityContext.setUnique(unique); - this.#isTrashedContext.setIsTrashed(data.isTrashed); - this.setIsNew(false); - this.#data.setPersisted(data); - this.#data.setCurrent(data); + if (response.data) { + this.#isTrashedContext.setIsTrashed(response.data.isTrashed); } - this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'umbDocumentStoreObserver'); - } - - #onStoreChange(entity: EntityModel | undefined) { - if (!entity) { - //TODO: This solution is alright for now. But reconsider when we introduce signal-r - history.pushState(null, '', UMB_DOCUMENTS_SECTION_PATH); - } + return response; } async create(parent: UmbEntityModel, documentTypeUnique: string, blueprintUnique?: string) { - this.resetState(); - this.#parent.setValue(parent); - if (blueprintUnique) { const blueprintRepository = new UmbDocumentBlueprintDetailRepository(this); const { data } = await blueprintRepository.requestByUnique(blueprintUnique); - this.#getDataPromise = this.repository.createScaffold({ - documentType: data?.documentType, - values: data?.values, - variants: data?.variants as Array, + return this.createScaffold({ + parent, + preset: { + documentType: data?.documentType, + values: data?.values, + variants: data?.variants as Array, + }, }); - } else { - this.#getDataPromise = this.repository.createScaffold({ + } + + return this.createScaffold({ + parent, + preset: { documentType: { unique: documentTypeUnique, collection: null, }, - }); - } - - const { data } = await this.#getDataPromise; - if (!data) return undefined; - - this.#entityContext.setEntityType(UMB_DOCUMENT_ENTITY_TYPE); - this.#entityContext.setUnique(data.unique); - this.#isTrashedContext.setIsTrashed(data.isTrashed); - this.setIsNew(true); - this.#data.setPersisted(undefined); - this.#data.setCurrent(data); - return data; + }, + }); } getCollectionAlias() { return UMB_DOCUMENT_COLLECTION_ALIAS; } - getData() { - return this.#data.getCurrent(); - } - - getUnique() { - return this.getData()?.unique; - } - - getEntityType() { - return UMB_DOCUMENT_ENTITY_TYPE; - } - - getContentTypeId() { - return this.getData()?.documentType.unique; - } - - getVaries() { - return this.#varies; - } - getVariesByCulture() { - return this.#variesByCulture; - } - getVariesBySegment() { - return this.#variesBySegment; - } - - variantById(variantId: UmbVariantId) { - return this.#data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x))); - } - - getVariant(variantId: UmbVariantId) { - return this.#data.getCurrent()?.variants?.find((x) => variantId.compare(x)); - } - - getName(variantId?: UmbVariantId) { - const variants = this.#data.getCurrent()?.variants; - if (!variants) return; - if (variantId) { - return variants.find((x) => variantId.compare(x))?.name; - } else { - return variants[0]?.name; - } - } - - setName(name: string, variantId?: UmbVariantId) { - this.#data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); - } - - name(variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '', - ); - } - - setTemplate(templateUnique: string) { - this.#data.updateCurrent({ template: { unique: templateUnique } }); - } - - async propertyStructureById(propertyId: string) { - return this.structure.propertyStructureById(propertyId); - } - /** - * @function propertyValueByAlias - * @param {string} propertyAlias - The alias of the property - * @param {UmbVariantId} variantId - The variant - * @returns {Promise | undefined>} - An observable for the value of the property - * @description Get an Observable for the value of this property. + * Gets the unique identifier of the content type. + * @deprecated Use `getContentTypeUnique` instead. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbDocumentWorkspaceContext */ - async propertyValueByAlias( - propertyAlias: string, - variantId?: UmbVariantId, - ): Promise | undefined> { - return this.#data.createObservablePartOfCurrent( - (data) => - data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true)) - ?.value as PropertyValueType, - ); + getContentTypeId(): string | undefined { + return this.getContentTypeUnique(); } /** - * Get the current value of the property with the given alias and variantId. - * @param {string} alias - The alias of the property - * @param {UmbVariantId | undefined} variantId - The variant id of the property - * @returns {ReturnType | undefined} The value or undefined if not set or found. + * Gets the unique identifier of the content type. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbDocumentWorkspaceContext */ - getPropertyValue(alias: string, variantId?: UmbVariantId) { - const currentData = this.#data.getCurrent(); - if (currentData) { - const newDataSet = currentData.values?.find( - (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), - ); - return newDataSet?.value as ReturnType; - } - return undefined; - } - async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { - this.initiatePropertyValueChange(); - variantId ??= UmbVariantId.CreateInvariant(); - const property = await this.structure.getPropertyStructureByAlias(alias); - - if (!property) { - throw new Error(`Property alias "${alias}" not found.`); - } - - const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); - if (!editorAlias) { - throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); - } - - const entry = { ...variantId.toObject(), alias, editorAlias, value } as UmbDocumentValueModel; - - const currentData = this.getData(); - if (currentData) { - const values = appendToFrozenArray( - currentData.values ?? [], - entry, - (x) => x.alias === alias && variantId!.compare(x), - ); - this.#data.updateCurrent({ values }); - - // TODO: We should move this type of logic to the act of saving [NL] - this.#data.ensureVariantData(variantId); - } - this.finishPropertyValueChange(); - } - - initiatePropertyValueChange() { - this.#data.initiatePropertyValueChange(); - } - finishPropertyValueChange = () => { - this.#data.finishPropertyValueChange(); - }; - - async #determineVariantOptions() { - const options = await firstValueFrom(this.variantOptions); - - const activeVariants = this.splitView.getActiveVariants(); - const activeVariantIds = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant)); - const changedVariantIds = this.#data.getChangedVariants(); - const selectedVariantIds = activeVariantIds.concat(changedVariantIds); - - // Selected can contain entries that are not part of the options, therefor the modal filters selection based on options. - const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture); - let selected = selectedVariantIds.map((x) => x.toString()).filter((v, i, a) => a.indexOf(v) === i); - selected = selected.filter((x) => readOnlyCultures.includes(x) === false); - - return { - options, - selected, - }; + getContentTypeUnique(): string | undefined { + return this.getData()?.documentType.unique; } - async #performSaveOrCreate(variantIds: Array, saveData: UmbDocumentDetailModel): Promise { - if (this.getIsNew()) { - // Create: - const parent = this.#parent.getValue(); - if (!parent) throw new Error('Parent is not set'); - - const { data, error } = await this.repository.create(saveData, parent.unique); - if (!data || error) { - throw new Error('Error creating document'); - } - - this.setIsNew(false); - this.#data.setPersisted(data); - // TODO: Only update the variants that was chosen to be saved: - const currentData = this.#data.getCurrent(); - - const variantIdsIncludingInvariant = [...variantIds, UmbVariantId.CreateInvariant()]; - - const newCurrentData = await new UmbMergeContentVariantDataController(this).process( - currentData, - data, - variantIds, - variantIdsIncludingInvariant, - ); - this.#data.setCurrent(newCurrentData); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadChildrenOfEntityEvent({ - entityType: parent.entityType, - unique: parent.unique, - }); - eventContext.dispatchEvent(event); - } else { - // Save: - const { data, error } = await this.repository.save(saveData); - if (!data || error) { - throw new Error('Error saving document'); - } - - this.#data.setPersisted(data); - // TODO: Only update the variants that was chosen to be saved: - const currentData = this.#data.getCurrent(); - - const variantIdsIncludingInvariant = [...variantIds, UmbVariantId.CreateInvariant()]; - - const newCurrentData = await new UmbMergeContentVariantDataController(this).process( - currentData, - data, - variantIds, - variantIdsIncludingInvariant, - ); - this.#data.setCurrent(newCurrentData); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - entityType: this.getEntityType(), - unique: this.getUnique()!, - }); - - eventContext.dispatchEvent(event); - } + /** + * Set the template + * @param {string} templateUnique The unique identifier of the template. + * @memberof UmbDocumentWorkspaceContext + */ + setTemplate(templateUnique: string) { + this._data.updateCurrent({ template: { unique: templateUnique } }); } - #readOnlyLanguageVariantsFilter = (option: UmbDocumentVariantOptionModel) => { - const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture); - return readOnlyCultures.includes(option.culture) === false; - }; - async #handleSaveAndPreview() { const unique = this.getUnique(); if (!unique) throw new Error('Unique is missing'); @@ -593,13 +215,13 @@ export class UmbDocumentWorkspaceContext let culture = UMB_INVARIANT_CULTURE; // Save document (the active variant) before previewing. - const { selected } = await this.#determineVariantOptions(); + const { selected } = await this._determineVariantOptions(); if (selected.length > 0) { culture = selected[0]; const variantIds = [UmbVariantId.FromString(culture)]; - const saveData = await this.#data.constructData(variantIds); - await this.#runMandatoryValidationForSaveData(saveData); - await this.#performSaveOrCreate(variantIds, saveData); + const saveData = await this._data.constructData(variantIds); + await this._runMandatoryValidationForSaveData(saveData); + await this._performCreateOrUpdate(variantIds, saveData); } // Tell the server that we're entering preview mode. @@ -624,7 +246,7 @@ export class UmbDocumentWorkspaceContext let variantIds: Array = []; - const { options, selected } = await this.#determineVariantOptions(); + const { options, selected } = await this._determineVariantOptions(); // If there is only one variant, we don't need to open the modal. if (options.length === 0) { @@ -639,7 +261,7 @@ export class UmbDocumentWorkspaceContext .open(this, UMB_DOCUMENT_PUBLISH_MODAL, { data: { options, - pickableFilter: this.#readOnlyLanguageVariantsFilter, + pickableFilter: this._readOnlyLanguageVariantsFilter, }, value: { selection: selected }, }) @@ -651,15 +273,15 @@ export class UmbDocumentWorkspaceContext variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; } - const saveData = await this.#data.constructData(variantIds); - await this.#runMandatoryValidationForSaveData(saveData); + const saveData = await this._data.constructData(variantIds); + await this._runMandatoryValidationForSaveData(saveData); // Create the validation repository if it does not exist. (we first create this here when we need it) [NL] this.#validationRepository ??= new UmbDocumentValidationRepository(this); // We ask the server first to get a concatenated set of validation messages. So we see both front-end and back-end validation messages [NL] if (this.getIsNew()) { - const parent = this.#parent.getValue(); + const parent = this.getParent(); if (!parent) throw new Error('Parent is not set'); this.#serverValidation.askServerForValidation( saveData, @@ -679,7 +301,7 @@ export class UmbDocumentWorkspaceContext }, async () => { // If data of the selection is not valid Then just save: - await this.#performSaveOrCreate(variantIds, saveData); + await this._performCreateOrUpdate(variantIds, saveData); // Notifying that the save was successful, but we did not publish, which is what we want to symbolize here. [NL] const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); // TODO: Get rid of the save notification. @@ -693,28 +315,11 @@ export class UmbDocumentWorkspaceContext ); } - async #runMandatoryValidationForSaveData(saveData: UmbDocumentDetailModel) { - // Check that the data is valid before we save it. - // Check variants have a name: - const variantsWithoutAName = saveData.variants.filter((x) => !x.name); - if (variantsWithoutAName.length > 0) { - const validationContext = await this.getContext(UMB_VALIDATION_CONTEXT); - variantsWithoutAName.forEach((variant) => { - validationContext.messages.addMessage( - 'client', - `$.variants[${UmbDataPathVariantQuery(variant)}].name`, - UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, - ); - }); - throw new Error('All variants must have a name'); - } - } - async #performSaveAndPublish(variantIds: Array, saveData: UmbDocumentDetailModel): Promise { const unique = this.getUnique(); if (!unique) throw new Error('Unique is missing'); - await this.#performSaveOrCreate(variantIds, saveData); + await this._performCreateOrUpdate(variantIds, saveData); const { error } = await this.publishingRepository.publish( unique, @@ -732,52 +337,6 @@ export class UmbDocumentWorkspaceContext } } - async #handleSave() { - const { options, selected } = await this.#determineVariantOptions(); - - let variantIds: Array = []; - - // If there is only one variant, we don't need to open the modal. - if (options.length === 0) { - throw new Error('No variants are available'); - } else if (options.length === 1) { - // If only one option we will skip ahead and save the document with the only variant available: - variantIds.push(UmbVariantId.Create(options[0])); - } else { - // If there are multiple variants, we will open the modal to let the user pick which variants to save. - const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - const result = await modalManagerContext - .open(this, UMB_DOCUMENT_SAVE_MODAL, { - data: { - options, - pickableFilter: this.#readOnlyLanguageVariantsFilter, - }, - value: { selection: selected }, - }) - .onSubmit() - .catch(() => undefined); - - if (!result?.selection.length) return; - - variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; - } - - const saveData = await this.#data.constructData(variantIds); - await this.#runMandatoryValidationForSaveData(saveData); - return await this.#performSaveOrCreate(variantIds, saveData); - } - - public override requestSubmit() { - return this.#handleSave(); - } - - public submit() { - return this.#handleSave(); - } - public override invalidSubmit() { - return this.#handleSave(); - } - public async publish() { throw new Error('Method not implemented.'); } @@ -791,14 +350,14 @@ export class UmbDocumentWorkspaceContext } public async schedule() { - const { options, selected } = await this.#determineVariantOptions(); + const { options, selected } = await this._determineVariantOptions(); const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); const result = await modalManagerContext .open(this, UMB_DOCUMENT_SCHEDULE_MODAL, { data: { options, - pickableFilter: this.#readOnlyLanguageVariantsFilter, + pickableFilter: this._readOnlyLanguageVariantsFilter, }, value: { selection: selected.map((unique) => ({ unique, schedule: {} })) }, }) @@ -840,14 +399,14 @@ export class UmbDocumentWorkspaceContext const entityType = this.getEntityType(); if (!entityType) throw new Error('Entity type is missing'); - const { options, selected } = await this.#determineVariantOptions(); + const { options, selected } = await this._determineVariantOptions(); const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); const result = await modalManagerContext .open(this, UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL, { data: { options, - pickableFilter: this.#readOnlyLanguageVariantsFilter, + pickableFilter: this._readOnlyLanguageVariantsFilter, }, value: { selection: selected }, }) @@ -887,25 +446,12 @@ export class UmbDocumentWorkspaceContext } } - async delete() { - const id = this.getUnique(); - if (id) { - await this.repository.delete(id); - } - } - public createPropertyDatasetContext( host: UmbControllerHost, variantId: UmbVariantId, ): UmbDocumentPropertyDatasetContext { return new UmbDocumentPropertyDatasetContext(host, this, variantId); } - - public override destroy(): void { - this.structure.destroy(); - this.#languageRepository.destroy(); - super.destroy(); - } } export default UmbDocumentWorkspaceContext; diff --git a/src/packages/extension-insights/collection/views/table/extension-table-collection-view.element.ts b/src/packages/extension-insights/collection/views/table/extension-table-collection-view.element.ts index 4300dd8062..64f6e4b90b 100644 --- a/src/packages/extension-insights/collection/views/table/extension-table-collection-view.element.ts +++ b/src/packages/extension-insights/collection/views/table/extension-table-collection-view.element.ts @@ -6,8 +6,6 @@ import { css, html, customElement, state } from '@umbraco-cms/backoffice/externa import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import './extension-table-entity-actions-column-layout.element.js'; - @customElement('umb-extension-table-collection-view') export class UmbExtensionTableCollectionViewElement extends UmbLitElement { @state() @@ -35,8 +33,7 @@ export class UmbExtensionTableCollectionViewElement extends UmbLitElement { }, { name: '', - alias: 'extensionAction', - elementName: 'umb-extension-table-entity-actions-column-layout', + alias: 'entityActions', }, ]; @@ -81,8 +78,12 @@ export class UmbExtensionTableCollectionViewElement extends UmbLitElement { value: extension.weight, }, { - columnAlias: 'extensionAction', - value: extension, + columnAlias: 'entityActions', + value: html``, }, ], }; diff --git a/src/packages/extension-insights/collection/views/table/extension-table-entity-actions-column-layout.element.ts b/src/packages/extension-insights/collection/views/table/extension-table-entity-actions-column-layout.element.ts deleted file mode 100644 index fed6e96a43..0000000000 --- a/src/packages/extension-insights/collection/views/table/extension-table-entity-actions-column-layout.element.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { UmbExtensionDetailModel } from '../../types.js'; -import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; - -const elementName = 'umb-extension-table-entity-actions-column-layout'; -@customElement(elementName) -export class UmbExtensionTableEntityActionsColumnLayoutElement extends UmbLitElement { - @property({ attribute: false }) - value!: UmbExtensionDetailModel; - - @state() - _isOpen = false; - - #onActionExecuted() { - this._isOpen = false; - } - - override render() { - return html` - - - - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - [elementName]: UmbExtensionTableEntityActionsColumnLayoutElement; - } -} diff --git a/src/packages/extension-insights/workspace/manifests.ts b/src/packages/extension-insights/workspace/manifests.ts index 6cd95677fb..6ea2f304e3 100644 --- a/src/packages/extension-insights/workspace/manifests.ts +++ b/src/packages/extension-insights/workspace/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_EXTENSION_COLLECTION_ALIAS } from '../collection/manifests.js'; import { UMB_EXTENSION_ROOT_ENTITY_TYPE } from '../entity.js'; import { UMB_EXTENSION_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/language/collection/views/table/column-layouts/entity-actions/language-table-entity-actions-column-layout.element.ts b/src/packages/language/collection/views/table/column-layouts/entity-actions/language-table-entity-actions-column-layout.element.ts deleted file mode 100644 index d4479a0402..0000000000 --- a/src/packages/language/collection/views/table/column-layouts/entity-actions/language-table-entity-actions-column-layout.element.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { UmbLanguageDetailModel } from '../../../../../types.js'; -import { html, nothing, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; - -@customElement('umb-language-table-entity-actions-column-layout') -export class UmbLanguageTableEntityActionsColumnLayoutElement extends UmbLitElement { - @property({ attribute: false }) - value!: UmbLanguageDetailModel; - - @state() - _isOpen = false; - - #onActionExecuted() { - this._isOpen = false; - } - - override render() { - // TODO: we need to use conditionals on each action here. But until we have that in place - // we'll just remove all actions on the default language. - if (this.value.isDefault) return nothing; - - return html` - - - - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-language-table-entity-actions-column-layout': UmbLanguageTableEntityActionsColumnLayoutElement; - } -} diff --git a/src/packages/language/collection/views/table/language-table-collection-view.element.ts b/src/packages/language/collection/views/table/language-table-collection-view.element.ts index ed2e9b86de..1a37448b3f 100644 --- a/src/packages/language/collection/views/table/language-table-collection-view.element.ts +++ b/src/packages/language/collection/views/table/language-table-collection-view.element.ts @@ -6,9 +6,7 @@ import { css, html, customElement, state } from '@umbraco-cms/backoffice/externa import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import './column-layouts/boolean/language-table-boolean-column-layout.element.js'; import './column-layouts/name/language-table-name-column-layout.element.js'; -import './column-layouts/entity-actions/language-table-entity-actions-column-layout.element.js'; @customElement('umb-language-table-collection-view') export class UmbLanguageTableCollectionViewElement extends UmbLitElement { @@ -31,12 +29,10 @@ export class UmbLanguageTableCollectionViewElement extends UmbLitElement { { name: 'Default', alias: 'defaultLanguage', - elementName: 'umb-language-table-boolean-column-layout', }, { name: 'Mandatory', alias: 'mandatoryLanguage', - elementName: 'umb-language-table-boolean-column-layout', }, { name: 'Fallback', @@ -45,7 +41,6 @@ export class UmbLanguageTableCollectionViewElement extends UmbLitElement { { name: '', alias: 'entityActions', - elementName: 'umb-language-table-entity-actions-column-layout', }, ]; @@ -88,11 +83,11 @@ export class UmbLanguageTableCollectionViewElement extends UmbLitElement { }, { columnAlias: 'defaultLanguage', - value: language.isDefault, + value: html``, }, { columnAlias: 'mandatoryLanguage', - value: language.isMandatory, + value: html``, }, { columnAlias: 'fallbackLanguage', @@ -100,7 +95,11 @@ export class UmbLanguageTableCollectionViewElement extends UmbLitElement { }, { columnAlias: 'entityActions', - value: language, + value: html``, }, ], }; diff --git a/src/packages/language/workspace/language-root/manifests.ts b/src/packages/language/workspace/language-root/manifests.ts index e0fef2efe7..92e59e4341 100644 --- a/src/packages/language/workspace/language-root/manifests.ts +++ b/src/packages/language/workspace/language-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_LANGUAGE_COLLECTION_ALIAS } from '../../collection/index.js'; import { UMB_LANGUAGE_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_LANGUAGE_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { @@ -27,7 +27,7 @@ export const manifests: Array = [ }, conditions: [ { - alias:UMB_WORKSPACE_CONDITION_ALIAS, + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_LANGUAGE_ROOT_WORKSPACE_ALIAS, }, ], diff --git a/src/packages/language/workspace/language/language-workspace.context.ts b/src/packages/language/workspace/language/language-workspace.context.ts index 97b2dc8507..00c28e9870 100644 --- a/src/packages/language/workspace/language/language-workspace.context.ts +++ b/src/packages/language/workspace/language/language-workspace.context.ts @@ -32,7 +32,7 @@ export class UmbLanguageWorkspaceContext path: 'create', component: UmbLanguageWorkspaceEditorElement, setup: async () => { - this.createScaffold({ parent: { entityType: UMB_LANGUAGE_ROOT_ENTITY_TYPE, unique: null } }); + await this.createScaffold({ parent: { entityType: UMB_LANGUAGE_ROOT_ENTITY_TYPE, unique: null } }); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/language/workspace/language/manifests.ts b/src/packages/language/workspace/language/manifests.ts index b0e3508017..e3e45525fa 100644 --- a/src/packages/language/workspace/language/manifests.ts +++ b/src/packages/language/workspace/language/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_LANGUAGE_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/media/media-types/menu/manifests.ts b/src/packages/media/media-types/menu/manifests.ts index 7295ae54db..b3a58cb417 100644 --- a/src/packages/media/media-types/menu/manifests.ts +++ b/src/packages/media/media-types/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEDIA_TYPE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/media/media-types/tree/folder/workspace/manifests.ts b/src/packages/media/media-types/tree/folder/workspace/manifests.ts index 00f6310476..6fa8991742 100644 --- a/src/packages/media/media-types/tree/folder/workspace/manifests.ts +++ b/src/packages/media/media-types/tree/folder/workspace/manifests.ts @@ -1,7 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_MEDIA_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/media/media-types/workspace/manifests.ts b/src/packages/media/media-types/workspace/manifests.ts index 5fd768d67b..6076139d14 100644 --- a/src/packages/media/media-types/workspace/manifests.ts +++ b/src/packages/media/media-types/workspace/manifests.ts @@ -1,8 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEDIA_TYPE_COMPOSITION_REPOSITORY_ALIAS } from '../repository/index.js'; import { UMB_MEDIA_TYPE_WORKSPACE_ALIAS } from './constants.js'; - -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/media/media-types/workspace/media-type-workspace.context.ts b/src/packages/media/media-types/workspace/media-type-workspace.context.ts index e5c23009f2..ae9c12ae11 100644 --- a/src/packages/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/packages/media/media-types/workspace/media-type-workspace.context.ts @@ -81,10 +81,10 @@ export class UmbMediaTypeWorkspaceContext { path: 'create/parent/:entityType/:parentUnique', component: UmbMediaTypeWorkspaceEditorElement, - setup: (_component, info) => { + setup: async (_component, info) => { const parentEntityType = info.match.params.entityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; - this.create({ entityType: parentEntityType, unique: parentUnique }); + await this.create({ entityType: parentEntityType, unique: parentUnique }); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/media/media/collection/action/create-media-collection-action.element.ts b/src/packages/media/media/collection/action/create-media-collection-action.element.ts index 0b3faf83a1..1a14b5ec76 100644 --- a/src/packages/media/media/collection/action/create-media-collection-action.element.ts +++ b/src/packages/media/media/collection/action/create-media-collection-action.element.ts @@ -9,6 +9,7 @@ import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import type { ManifestCollectionAction } from '@umbraco-cms/backoffice/collection'; import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; @customElement('umb-create-media-collection-action') export class UmbCreateMediaCollectionActionElement extends UmbLitElement { @@ -22,7 +23,7 @@ export class UmbCreateMediaCollectionActionElement extends UmbLitElement { private _currentView?: string; @state() - private _mediaUnique?: string; + private _mediaUnique?: UmbEntityUnique; @state() private _mediaTypeUnique?: string; @@ -54,6 +55,7 @@ export class UmbCreateMediaCollectionActionElement extends UmbLitElement { this.observe(workspaceContext.unique, (unique) => { this._mediaUnique = unique; }); + this.observe(workspaceContext.contentTypeUnique, (mediaTypeUnique) => { this._mediaTypeUnique = mediaTypeUnique; }); diff --git a/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts b/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts index 0d28d08609..19f90522ed 100644 --- a/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts +++ b/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts @@ -125,9 +125,9 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { }; }); - this._tableColumns = [...this.#systemColumns, ...userColumns]; + this._tableColumns = [...this.#systemColumns, ...userColumns, { name: '', alias: 'entityActions' }]; } else { - this._tableColumns = [...this.#systemColumns]; + this._tableColumns = [...this.#systemColumns, { name: '', alias: 'entityActions' }]; } } @@ -141,6 +141,17 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { const data = this._tableColumns?.map((column) => { + if (column.alias === 'entityActions') { + return { + columnAlias: 'entityActions', + value: html``, + }; + } + const editPath = this.#routeBuilder ? this.#routeBuilder({ entityType: item.entityType }) + UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique }) diff --git a/src/packages/media/media/components/input-image-cropper/crop-change.event.ts b/src/packages/media/media/components/input-image-cropper/crop-change.event.ts index 0c9b076fba..b812509444 100644 --- a/src/packages/media/media/components/input-image-cropper/crop-change.event.ts +++ b/src/packages/media/media/components/input-image-cropper/crop-change.event.ts @@ -5,4 +5,4 @@ export class UmbImageCropChangeEvent extends Event { // mimics the native change event super(UmbImageCropChangeEvent.TYPE, { bubbles: false, composed: false, cancelable: false, ...args }); } -} \ No newline at end of file +} diff --git a/src/packages/media/media/components/input-image-cropper/focalpoint-change.event.ts b/src/packages/media/media/components/input-image-cropper/focalpoint-change.event.ts index ba38c5e87f..596a03b559 100644 --- a/src/packages/media/media/components/input-image-cropper/focalpoint-change.event.ts +++ b/src/packages/media/media/components/input-image-cropper/focalpoint-change.event.ts @@ -10,4 +10,4 @@ export class UmbFocalPointChangeEvent extends Event { super(UmbFocalPointChangeEvent.TYPE, { bubbles: false, composed: false, cancelable: false, ...args }); this.focalPoint = focalPoint; } -} \ No newline at end of file +} diff --git a/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts b/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts index 3ca9812849..5ba36c22b8 100644 --- a/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts +++ b/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts @@ -1,19 +1,20 @@ -import './image-cropper.element.js'; -import './image-cropper-focus-setter.element.js'; -import './image-cropper-preview.element.js'; import type { UmbImageCropperElement } from './image-cropper.element.js'; import type { UmbImageCropperCrop, UmbImageCropperCrops, UmbImageCropperFocalPoint, UmbImageCropperPropertyEditorValue, - UmbFocalPointChangeEvent, - UmbImageCropChangeEvent, -} from './index.js'; +} from './types.js'; +import type { UmbImageCropChangeEvent } from './crop-change.event.js'; +import type { UmbFocalPointChangeEvent } from './focalpoint-change.event.js'; import { css, customElement, html, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import './image-cropper.element.js'; +import './image-cropper-focus-setter.element.js'; +import './image-cropper-preview.element.js'; + @customElement('umb-image-cropper-field') export class UmbInputImageCropperFieldElement extends UmbLitElement { @property({ attribute: false }) diff --git a/src/packages/media/media/components/input-image-cropper/image-cropper-focus-setter.element.ts b/src/packages/media/media/components/input-image-cropper/image-cropper-focus-setter.element.ts index 0963940875..5951fe8079 100644 --- a/src/packages/media/media/components/input-image-cropper/image-cropper-focus-setter.element.ts +++ b/src/packages/media/media/components/input-image-cropper/image-cropper-focus-setter.element.ts @@ -1,8 +1,19 @@ -import { type UmbImageCropperFocalPoint, UmbFocalPointChangeEvent } from './index.js'; import type { UmbFocalPointModel } from '../../types.js'; +import type { UmbImageCropperFocalPoint } from './types.js'; +import { UmbFocalPointChangeEvent } from './focalpoint-change.event.js'; import { drag } from '@umbraco-cms/backoffice/external/uui'; import { clamp } from '@umbraco-cms/backoffice/utils'; -import { css, customElement, classMap, ifDefined, html, nothing, state, property, query } from '@umbraco-cms/backoffice/external/lit'; +import { + css, + customElement, + classMap, + ifDefined, + html, + nothing, + state, + property, + query, +} from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; @@ -38,7 +49,7 @@ export class UmbImageCropperFocusSetterElement extends UmbLitElement { @property({ type: Boolean }) hideFocalPoint = false; - + @property({ type: Boolean, reflect: true }) disabled = false; @@ -84,7 +95,7 @@ export class UmbImageCropperFocusSetterElement extends UmbLitElement { } this.#resetCoords(); - + this.imageElement.style.aspectRatio = `${imageAspectRatio}`; this.wrapperElement.style.aspectRatio = `${imageAspectRatio}`; }; @@ -97,22 +108,21 @@ export class UmbImageCropperFocusSetterElement extends UmbLitElement { } #coordsToFactor(x: number, y: number) { - const top = (y / 100) / y * 50; - const left = (x / 100) / x * 50; + const top = (y / 100 / y) * 50; + const left = (x / 100 / x) * 50; - return { top, left }; + return { top, left }; } #setFocalPoint(x: number, y: number, width: number, height: number) { - - const left = clamp((x / width), 0, 1); - const top = clamp((y / height), 0, 1); + const left = clamp(x / width, 0, 1); + const top = clamp(y / height, 0, 1); this.#coordsToFactor(x, y); const focalPoint = { left, top } as UmbFocalPointModel; - console.log("setFocalPoint", focalPoint) + console.log('setFocalPoint', focalPoint); this.dispatchEvent(new UmbFocalPointChangeEvent(focalPoint)); } @@ -171,7 +181,7 @@ export class UmbImageCropperFocusSetterElement extends UmbLitElement { #handleGridKeyDown(event: KeyboardEvent) { if (this.disabled || this.hideFocalPoint) return; - + const increment = event.shiftKey ? 1 : 10; const grid = this.wrapperElement; @@ -207,14 +217,13 @@ export class UmbImageCropperFocusSetterElement extends UmbLitElement { override render() { if (!this.src) return nothing; return html` -
    +
    nothing} src=${this.src} alt="" /> -
    - - + +
    `; } diff --git a/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts b/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts index 24a92a2af8..c71255813e 100644 --- a/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts +++ b/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts @@ -1,10 +1,9 @@ -import { UmbInputMediaElement } from '../input-media/index.js'; import { UmbMediaItemRepository } from '../../repository/index.js'; import { UMB_IMAGE_CROPPER_EDITOR_MODAL, UMB_MEDIA_PICKER_MODAL } from '../../modals/index.js'; import type { UmbCropModel, UmbMediaPickerPropertyValue } from '../../types.js'; import type { UmbMediaItemModel } from '../../repository/index.js'; import type { UmbUploadableItem } from '../../dropzone/types.js'; -import { customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { umbConfirmModal, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbId } from '@umbraco-cms/backoffice/id'; @@ -414,7 +413,42 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement, `; } - static override styles = UmbInputMediaElement.styles; + static override styles = [ + css` + :host { + position: relative; + } + .container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + grid-auto-rows: 150px; + gap: var(--uui-size-space-5); + } + + #btn-add { + text-align: center; + height: 100%; + } + + uui-icon { + display: block; + margin: 0 auto; + } + + uui-card-media umb-icon { + font-size: var(--uui-size-8); + } + + uui-card-media[drag-placeholder] { + opacity: 0.2; + } + img { + background-image: url('data:image/svg+xml;charset=utf-8,'); + background-size: 10px 10px; + background-repeat: repeat; + } + `, + ]; } export default UmbInputRichMediaElement; diff --git a/src/packages/media/media/menu/manifests.ts b/src/packages/media/media/menu/manifests.ts index 1681aedd98..f9e666fec0 100644 --- a/src/packages/media/media/menu/manifests.ts +++ b/src/packages/media/media/menu/manifests.ts @@ -1,6 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEDIA_TREE_ALIAS } from '../tree/index.js'; import { UMB_MEDIA_MENU_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts b/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts index 46e8af9c85..224b4cbdbc 100644 --- a/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts +++ b/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts @@ -1,8 +1,8 @@ +import type { UmbInputMediaElement } from '../../components/index.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; -import type { UmbInputMediaElement } from '@umbraco-cms/backoffice/media'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, diff --git a/src/packages/media/media/tree/media-tree-picker-modal.token.ts b/src/packages/media/media/tree/media-tree-picker-modal.token.ts index 2ba9bba5cd..30cae5a481 100644 --- a/src/packages/media/media/tree/media-tree-picker-modal.token.ts +++ b/src/packages/media/media/tree/media-tree-picker-modal.token.ts @@ -1,10 +1,10 @@ +import type { UmbMediaTreeItemModel } from './types.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; import { type UmbTreePickerModalValue, type UmbTreePickerModalData, UMB_TREE_PICKER_MODAL_ALIAS, } from '@umbraco-cms/backoffice/tree'; -import type { UmbMediaTreeItemModel } from '@umbraco-cms/backoffice/media'; export type UmbMediaTreePickerModalData = UmbTreePickerModalData; export type UmbMediaTreePickerModalValue = UmbTreePickerModalValue; diff --git a/src/packages/media/media/workspace/manifests.ts b/src/packages/media/media/workspace/manifests.ts index a48480ee07..21daa964dd 100644 --- a/src/packages/media/media/workspace/manifests.ts +++ b/src/packages/media/media/workspace/manifests.ts @@ -1,7 +1,6 @@ -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; import { UMB_CONTENT_HAS_PROPERTIES_WORKSPACE_CONDITION } from '@umbraco-cms/backoffice/content'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_MEDIA_WORKSPACE_ALIAS = 'Umb.Workspace.Media'; diff --git a/src/packages/media/media/workspace/media-workspace.context.ts b/src/packages/media/media/workspace/media-workspace.context.ts index e22db20690..fa3dcd1a52 100644 --- a/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/packages/media/media/workspace/media-workspace.context.ts @@ -1,192 +1,58 @@ import { UmbMediaTypeDetailRepository } from '../../media-types/repository/detail/media-type-detail.repository.js'; import { UmbMediaPropertyDatasetContext } from '../property-dataset-context/media-property-dataset-context.js'; import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js'; -import { UmbMediaDetailRepository } from '../repository/index.js'; -import type { - UmbMediaDetailModel, - UmbMediaValueModel, - UmbMediaVariantModel, - UmbMediaVariantOptionModel, -} from '../types.js'; +import type { UmbMediaDetailRepository } from '../repository/index.js'; +import { UMB_MEDIA_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; +import type { UmbMediaDetailModel, UmbMediaVariantModel } from '../types.js'; import { UMB_CREATE_MEDIA_WORKSPACE_PATH_PATTERN, UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN } from '../paths.js'; -import { UMB_MEDIA_SECTION_PATH } from '../../media-section/paths.js'; import { UMB_MEDIA_COLLECTION_ALIAS } from '../collection/index.js'; import { UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD } from './constants.js'; -import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; +import { UMB_MEDIA_WORKSPACE_ALIAS } from './manifests.js'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { - UmbSubmittableWorkspaceContextBase, UmbWorkspaceIsNewRedirectController, UmbWorkspaceIsNewRedirectControllerAlias, - UmbWorkspaceSplitViewManager, } from '@umbraco-cms/backoffice/workspace'; -import { - appendToFrozenArray, - mergeObservables, - UmbArrayState, - UmbObjectState, -} from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; -import { - UmbRequestReloadChildrenOfEntityEvent, - UmbRequestReloadStructureForEntityEvent, -} from '@umbraco-cms/backoffice/entity-action'; import type { UmbMediaTypeDetailModel } from '@umbraco-cms/backoffice/media-type'; import { - UmbContentWorkspaceDataManager, + UmbContentDetailWorkspaceContextBase, type UmbContentCollectionWorkspaceContext, type UmbContentWorkspaceContext, } from '@umbraco-cms/backoffice/content'; -import { UmbEntityContext } from '@umbraco-cms/backoffice/entity'; import { UmbIsTrashedEntityContext } from '@umbraco-cms/backoffice/recycle-bin'; -import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; -import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; type ContentModel = UmbMediaDetailModel; type ContentTypeModel = UmbMediaTypeDetailModel; + export class UmbMediaWorkspaceContext - extends UmbSubmittableWorkspaceContextBase + extends UmbContentDetailWorkspaceContextBase< + ContentModel, + UmbMediaDetailRepository, + ContentTypeModel, + UmbMediaVariantModel + > implements UmbContentWorkspaceContext, UmbContentCollectionWorkspaceContext { - public readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const; - - public readonly repository = new UmbMediaDetailRepository(this); - - #parent = new UmbObjectState<{ entityType: string; unique: string | null } | undefined>(undefined); - readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined)); - readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined)); - - readonly #data = new UmbContentWorkspaceDataManager(this, UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD); - - #getDataPromise?: Promise; - // TODo: Optimize this so it uses either a App Language Context? [NL] - #languageRepository = new UmbLanguageCollectionRepository(this); - #languages = new UmbArrayState([], (x) => x.unique); - public readonly languages = this.#languages.asObservable(); - - readOnlyState = new UmbReadOnlyVariantStateManager(this); - - public isLoaded() { - return this.#getDataPromise; - } - - readonly unique = this.#data.createObservablePartOfCurrent((data) => data?.unique); - readonly entityType = this.#data.createObservablePartOfCurrent((data) => data?.entityType); - readonly contentTypeUnique = this.#data.createObservablePartOfCurrent((data) => data?.mediaType.unique); - readonly contentTypeHasCollection = this.#data.createObservablePartOfCurrent((data) => !!data?.mediaType.collection); + readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.mediaType.unique); + readonly contentTypeHasCollection = this._data.createObservablePartOfCurrent((data) => !!data?.mediaType.collection); - readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants || []); + readonly urls = this._data.createObservablePartOfCurrent((data) => data?.urls || []); - readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); - getValues() { - return this.#data.getCurrent()?.values; - } - - readonly urls = this.#data.createObservablePartOfCurrent((data) => data?.urls || []); - - readonly structure = new UmbContentTypeStructureManager(this, new UmbMediaTypeDetailRepository(this)); - readonly variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); - readonly variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); - readonly varies = this.structure.ownerContentTypeObservablePart((x) => - x ? x.variesByCulture || x.variesBySegment : undefined, - ); - #varies?: boolean; - #variesByCulture?: boolean; - #variesBySegment?: boolean; - - readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); - #dataTypeSchemaAliasMap = new Map(); - - readonly splitView = new UmbWorkspaceSplitViewManager(); - - readonly variantOptions = mergeObservables( - [this.varies, this.variants, this.languages], - ([varies, variants, languages]) => { - // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL] - if (varies === true) { - return languages.map((language) => { - return { - variant: variants.find((x) => x.culture === language.unique), - language, - // TODO: When including segments, this object should be updated to include a object for the segment. [NL] - // TODO: When including segments, the unique should be updated to include the segment as well. [NL] - unique: language.unique, // This must be a variantId string! - culture: language.unique, - segment: null, - } as UmbMediaVariantOptionModel; - }); - } else if (varies === false) { - return [ - { - variant: variants.find((x) => x.culture === null), - language: languages.find((x) => x.isDefault), - culture: null, - segment: null, - unique: UMB_INVARIANT_CULTURE, // This must be a variantId string! - } as UmbMediaVariantOptionModel, - ]; - } - return [] as Array; - }, - ); - - // TODO: this should be set up for all entity workspace contexts in a base class - #entityContext = new UmbEntityContext(this); - // TODO: this might not be the correct place to spin this up #isTrashedContext = new UmbIsTrashedEntityContext(this); constructor(host: UmbControllerHost) { - super(host, 'Umb.Workspace.Media'); + super(host, { + entityType: UMB_MEDIA_ENTITY_TYPE, + workspaceAlias: UMB_MEDIA_WORKSPACE_ALIAS, + detailRepositoryAlias: UMB_MEDIA_DETAIL_REPOSITORY_ALIAS, + contentTypeDetailRepository: UmbMediaTypeDetailRepository, + contentVariantScaffold: UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD, + }); this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); - this.observe( - this.varies, - (varies) => { - this.#data.setVaries(varies); - this.#varies = varies; - }, - null, - ); - this.observe( - this.variesByCulture, - (varies) => { - this.#data.setVariesByCulture(varies); - this.#variesByCulture = varies; - }, - null, - ); - this.observe( - this.variesBySegment, - (varies) => { - this.#data.setVariesBySegment(varies); - this.#variesBySegment = varies; - }, - null, - ); - this.observe( - this.structure.contentTypeDataTypeUniques, - (dataTypeUniques: Array) => { - this.#dataTypeItemManager.setUniques(dataTypeUniques); - }, - null, - ); - this.observe( - this.#dataTypeItemManager.items, - (dataTypes) => { - // Make a map of the data type unique and editorAlias: - this.#dataTypeSchemaAliasMap = new Map( - dataTypes.map((dataType) => { - return [dataType.unique, dataType.propertyEditorSchemaAlias]; - }), - ); - }, - null, - ); - this.loadLanguages(); this.routes.setRoutes([ { @@ -196,7 +62,10 @@ export class UmbMediaWorkspaceContext const parentEntityType = info.match.params.entityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; const mediaTypeUnique = info.match.params.mediaTypeUnique; - this.create({ entityType: parentEntityType, unique: parentUnique }, mediaTypeUnique); + await this.createScaffold({ + parent: { entityType: parentEntityType, unique: parentUnique }, + preset: { mediaType: { unique: mediaTypeUnique, collection: null } }, + }); new UmbWorkspaceIsNewRedirectController( this, @@ -217,245 +86,53 @@ export class UmbMediaWorkspaceContext ]); } - override resetState() { + public override resetState() { super.resetState(); - this.#data.clear(); - } - - async loadLanguages() { - // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] - const { data } = await this.#languageRepository.requestCollection({}); - this.#languages.setValue(data?.items ?? []); + this.removeUmbControllerByAlias(UmbWorkspaceIsNewRedirectControllerAlias); } - async load(unique: string) { - this.resetState(); - this.#getDataPromise = this.repository.requestByUnique(unique); - type GetDataType = Awaited>; - const { data, asObservable } = (await this.#getDataPromise) as GetDataType; + public override async load(unique: string) { + const response = await super.load(unique); - if (data) { - this.#entityContext.setEntityType(UMB_MEDIA_ENTITY_TYPE); - this.#entityContext.setUnique(unique); - this.#isTrashedContext.setIsTrashed(data.isTrashed); - this.setIsNew(false); - this.#data.setPersisted(data); - this.#data.setCurrent(data); + if (response.data) { + this.#isTrashedContext.setIsTrashed(response.data.isTrashed); } - this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'umbMediaStoreObserver'); - } - - #onStoreChange(entity: ContentModel | undefined) { - if (!entity) { - //TODO: This solution is alright for now. But reconsider when we introduce signal-r - history.pushState(null, '', UMB_MEDIA_SECTION_PATH); - } + return response; } - async create(parent: { entityType: string; unique: string | null }, mediaTypeUnique: string) { - this.resetState(); - this.#parent.setValue(parent); - this.#getDataPromise = this.repository.createScaffold({ mediaType: { unique: mediaTypeUnique, collection: null } }); - const { data } = await this.#getDataPromise; - if (!data) return undefined; - - this.#entityContext.setEntityType(UMB_MEDIA_ENTITY_TYPE); - this.#entityContext.setUnique(data.unique); - this.setIsNew(true); - this.#data.setPersisted(undefined); - this.#data.setCurrent(data); - return data; + /* + * @deprecated Use `createScaffold` instead. + */ + public async create(parent: { entityType: string; unique: string | null }, mediaTypeUnique: string) { + const args = { + parent, + preset: { mediaType: { unique: mediaTypeUnique, collection: null } }, + }; + return this.createScaffold(args); } - getCollectionAlias() { + public getCollectionAlias() { return UMB_MEDIA_COLLECTION_ALIAS; } - getData() { - return this.#data.getCurrent(); - } - - getUnique() { - return this.getData()?.unique; - } - - getEntityType() { - return UMB_MEDIA_ENTITY_TYPE; - } - - getContentTypeId() { - return this.getData()?.mediaType.unique; - } - - getVaries() { - return this.#varies; - } - getVariesByCulture() { - return this.#variesByCulture; - } - getVariesBySegment() { - return this.#variesBySegment; - } - - variantById(variantId: UmbVariantId) { - return this.#data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x))); - } - - getVariant(variantId: UmbVariantId) { - return this.#data.getCurrent()?.variants?.find((x) => variantId.compare(x)); - } - - getName(variantId?: UmbVariantId) { - const variants = this.#data.getCurrent()?.variants; - if (!variants) return; - if (variantId) { - return variants.find((x) => variantId.compare(x))?.name; - } else { - return variants[0]?.name; - } - } - - setName(name: string, variantId?: UmbVariantId) { - this.#data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); - } - - name(variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '', - ); - } - - async propertyStructureById(propertyId: string) { - return this.structure.propertyStructureById(propertyId); - } /** - * @function propertyValueByAlias - * @param {string} propertyAlias - * @param {UmbVariantId} variantId - * @returns {Promise | undefined>} - * @description Get an Observable for the value of this property. + * Gets the unique identifier of the content type. + * @deprecated Use `getContentTypeUnique` instead. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbMediaWorkspaceContext */ - async propertyValueByAlias(propertyAlias: string, variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => - data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true)) - ?.value as PropertyValueType, - ); + getContentTypeId(): string | undefined { + return this.getContentTypeUnique(); } /** - * Get the current value of the property with the given alias and variantId. - * @param {string} alias - * @param {UmbVariantId} variantId - * @returns The value or undefined if not set or found. + * Gets the unique identifier of the content type. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbMediaWorkspaceContext */ - getPropertyValue(alias: string, variantId?: UmbVariantId) { - const currentData = this.#data.getCurrent(); - if (currentData) { - const newDataSet = currentData.values?.find( - (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), - ); - return newDataSet?.value as ReturnType; - } - return undefined; - } - - async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { - this.initiatePropertyValueChange(); - variantId ??= UmbVariantId.CreateInvariant(); - const property = await this.structure.getPropertyStructureByAlias(alias); - - if (!property) { - throw new Error(`Property alias "${alias}" not found.`); - } - - const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); - if (!editorAlias) { - throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); - } - - const entry = { ...variantId.toObject(), alias, editorAlias, value } as UmbMediaValueModel; - - const currentData = this.getData(); - if (currentData) { - const values = appendToFrozenArray( - currentData.values ?? [], - entry, - (x) => x.alias === alias && variantId!.compare(x), - ); - this.#data.updateCurrent({ values }); - - // TODO: We should move this type of logic to the act of saving [NL] - this.#data.ensureVariantData(variantId); - } - this.finishPropertyValueChange(); - } - - initiatePropertyValueChange() { - this.#data.initiatePropertyValueChange(); - } - finishPropertyValueChange = () => { - this.#data.finishPropertyValueChange(); - }; - - async #handleSave() { - const saveData = this.#data.getCurrent(); - if (!saveData?.unique) throw new Error('Unique is missing'); - - if (this.getIsNew()) { - const parent = this.#parent.getValue(); - if (!parent) throw new Error('Parent is not set'); - - const { data, error } = await this.repository.create(saveData, parent.unique); - if (!data || error) { - throw new Error('Error creating document'); - } - - this.setIsNew(false); - this.#data.setPersisted(data); - this.#data.setCurrent(data); - - // TODO: this might not be the right place to alert the tree, but it works for now - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadChildrenOfEntityEvent({ - entityType: parent.entityType, - unique: parent.unique, - }); - eventContext.dispatchEvent(event); - } else { - // Save: - const { data, error } = await this.repository.save(saveData); - if (!data || error) { - throw new Error('Error saving document'); - } - - this.#data.setPersisted(data); - this.#data.setCurrent(data); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - unique: this.getUnique()!, - entityType: this.getEntityType(), - }); - - eventContext.dispatchEvent(event); - } - } - - async submit() { - const data = this.getData(); - if (!data) { - throw new Error('Data is missing'); - } - await this.#handleSave(); - } - - async delete() { - const id = this.getUnique(); - if (id) { - await this.repository.delete(id); - } + getContentTypeUnique(): string | undefined { + return this.getData()?.mediaType.unique; } public createPropertyDatasetContext( @@ -464,12 +141,6 @@ export class UmbMediaWorkspaceContext ): UmbMediaPropertyDatasetContext { return new UmbMediaPropertyDatasetContext(host, this, variantId); } - - public override destroy(): void { - this.structure.destroy(); - this.#languageRepository.destroy(); - super.destroy(); - } } export { UmbMediaWorkspaceContext as api }; diff --git a/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts b/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts index 90169d6666..4f9aaa6bb8 100644 --- a/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts +++ b/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts @@ -1,7 +1,7 @@ +import { UmbMediaReferenceRepository } from '../../../reference/index.js'; import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { isDefaultReference, isDocumentReference, isMediaReference } from '@umbraco-cms/backoffice/relations'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbMediaReferenceRepository } from '@umbraco-cms/backoffice/media'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; diff --git a/src/packages/media/media/workspace/views/info/media-workspace-view-info.element.ts b/src/packages/media/media/workspace/views/info/media-workspace-view-info.element.ts index 529d781978..2e49276698 100644 --- a/src/packages/media/media/workspace/views/info/media-workspace-view-info.element.ts +++ b/src/packages/media/media/workspace/views/info/media-workspace-view-info.element.ts @@ -1,3 +1,4 @@ +import { UMB_MEDIA_WORKSPACE_CONTEXT } from '../../media-workspace.context-token.js'; import { TimeOptions } from './utils.js'; import { css, customElement, html, ifDefined, nothing, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -5,7 +6,6 @@ import type { UmbMediaTypeItemModel } from '@umbraco-cms/backoffice/media-type'; import { UMB_MEDIA_TYPE_ENTITY_TYPE, UmbMediaTypeItemRepository } from '@umbraco-cms/backoffice/media-type'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UMB_MEDIA_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/media'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import type { MediaUrlInfoModel } from '@umbraco-cms/backoffice/external/backend-api'; diff --git a/src/packages/members/member-group/collection/views/table/member-group-table-collection-view.element.ts b/src/packages/members/member-group/collection/views/table/member-group-table-collection-view.element.ts index 2084d91556..a3ca46c992 100644 --- a/src/packages/members/member-group/collection/views/table/member-group-table-collection-view.element.ts +++ b/src/packages/members/member-group/collection/views/table/member-group-table-collection-view.element.ts @@ -19,6 +19,10 @@ export class UmbMemberGroupTableCollectionViewElement extends UmbLitElement { name: this.localize.term('general_name'), alias: 'memberGroupName', }, + { + name: '', + alias: 'entityActions', + }, ]; @state() @@ -52,6 +56,14 @@ export class UmbMemberGroupTableCollectionViewElement extends UmbLitElement { >${memberGroup.name}`, }, + { + columnAlias: 'entityActions', + value: html``, + }, ], }; }); diff --git a/src/packages/members/member-group/workspace/member-group-root/manifests.ts b/src/packages/members/member-group/workspace/member-group-root/manifests.ts index a066e19b59..ce7e83d833 100644 --- a/src/packages/members/member-group/workspace/member-group-root/manifests.ts +++ b/src/packages/members/member-group/workspace/member-group-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEMBER_GROUP_COLLECTION_ALIAS } from '../../collection/manifests.js'; import { UMB_MEMBER_GROUP_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_MEMBER_GROUP_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/members/member-group/workspace/member-group/manifests.ts b/src/packages/members/member-group/workspace/member-group/manifests.ts index ebfd58fccb..c5811568f3 100644 --- a/src/packages/members/member-group/workspace/member-group/manifests.ts +++ b/src/packages/members/member-group/workspace/member-group/manifests.ts @@ -4,8 +4,7 @@ import type { ManifestWorkspaceActions, ManifestWorkspaceView, } from '@umbraco-cms/backoffice/workspace'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_MEMBER_GROUP_WORKSPACE_ALIAS = 'Umb.Workspace.MemberGroup'; diff --git a/src/packages/members/member-group/workspace/member-group/member-group-workspace.context.ts b/src/packages/members/member-group/workspace/member-group/member-group-workspace.context.ts index fd2daebf9e..7b8c8f5687 100644 --- a/src/packages/members/member-group/workspace/member-group/member-group-workspace.context.ts +++ b/src/packages/members/member-group/workspace/member-group/member-group-workspace.context.ts @@ -29,8 +29,8 @@ export class UmbMemberGroupWorkspaceContext { path: 'create', component: UmbMemberGroupWorkspaceEditorElement, - setup: () => { - this.createScaffold({ parent: { entityType: UMB_USER_GROUP_ROOT_ENTITY_TYPE, unique: null } }); + setup: async () => { + await this.createScaffold({ parent: { entityType: UMB_USER_GROUP_ROOT_ENTITY_TYPE, unique: null } }); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/members/member-type/menu/manifests.ts b/src/packages/members/member-type/menu/manifests.ts index 39f0b5b1e5..85ca885488 100644 --- a/src/packages/members/member-type/menu/manifests.ts +++ b/src/packages/members/member-type/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEMBER_TYPE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/members/member-type/workspace/manifests.ts b/src/packages/members/member-type/workspace/manifests.ts index 1d9925c118..80d973d302 100644 --- a/src/packages/members/member-type/workspace/manifests.ts +++ b/src/packages/members/member-type/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEMBER_TYPE_COMPOSITION_REPOSITORY_ALIAS } from '../repository/index.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const UMB_MEMBER_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.MemberType'; diff --git a/src/packages/members/member-type/workspace/member-type-workspace.context.ts b/src/packages/members/member-type/workspace/member-type-workspace.context.ts index e791fc1e19..5cb6063562 100644 --- a/src/packages/members/member-type/workspace/member-type-workspace.context.ts +++ b/src/packages/members/member-type/workspace/member-type-workspace.context.ts @@ -77,10 +77,10 @@ export class UmbMemberTypeWorkspaceContext { path: 'create/parent/:entityType/:parentUnique', component: UmbMemberTypeWorkspaceEditorElement, - setup: (_component, info) => { + setup: async (_component, info) => { const parentEntityType = info.match.params.entityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; - this.create({ entityType: parentEntityType, unique: parentUnique }); + await this.create({ entityType: parentEntityType, unique: parentUnique }); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/members/member/collection/views/table/member-table-collection-view.element.ts b/src/packages/members/member/collection/views/table/member-table-collection-view.element.ts index 8fca9ecca1..d7f0b3abcf 100644 --- a/src/packages/members/member/collection/views/table/member-table-collection-view.element.ts +++ b/src/packages/members/member/collection/views/table/member-table-collection-view.element.ts @@ -8,8 +8,6 @@ import { css, html, customElement, state } from '@umbraco-cms/backoffice/externa import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbMemberTypeItemRepository } from '@umbraco-cms/backoffice/member-type'; -import './member-table-entity-actions-column-layout.element.js'; - @customElement('umb-member-table-collection-view') export class UmbMemberTableCollectionViewElement extends UmbLitElement { @state() @@ -42,7 +40,6 @@ export class UmbMemberTableCollectionViewElement extends UmbLitElement { { name: '', alias: 'entityActions', - elementName: 'umb-member-table-entity-actions-column-layout', }, ]; @@ -106,10 +103,11 @@ export class UmbMemberTableCollectionViewElement extends UmbLitElement { }, { columnAlias: 'entityActions', - value: { - entityType: member.entityType, - unique: member.unique, - }, + value: html``, }, ], }; diff --git a/src/packages/members/member/collection/views/table/member-table-entity-actions-column-layout.element.ts b/src/packages/members/member/collection/views/table/member-table-entity-actions-column-layout.element.ts deleted file mode 100644 index 61eab8c400..0000000000 --- a/src/packages/members/member/collection/views/table/member-table-entity-actions-column-layout.element.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { UmbMemberEntityType } from '../../../entity.js'; -import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; - -const elementName = 'umb-member-table-entity-actions-column-layout'; -@customElement(elementName) -export class UmbMemberTableEntityActionsColumnLayoutElement extends UmbLitElement { - @property({ attribute: false }) - value!: { - unique: string; - entityType: UmbMemberEntityType; - }; - - @state() - _isOpen = false; - - #onActionExecuted() { - this._isOpen = false; - } - - override render() { - return html` - - - - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - [elementName]: UmbMemberTableEntityActionsColumnLayoutElement; - } -} diff --git a/src/packages/members/member/workspace/member-root/manifests.ts b/src/packages/members/member/workspace/member-root/manifests.ts index bfc4101ec5..6b3c12a77e 100644 --- a/src/packages/members/member/workspace/member-root/manifests.ts +++ b/src/packages/members/member/workspace/member-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEMBER_COLLECTION_ALIAS } from '../../collection/manifests.js'; import { UMB_MEMBER_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_MEMBER_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/members/member/workspace/member/manifests.ts b/src/packages/members/member/workspace/member/manifests.ts index cb8be75248..aac2a61278 100644 --- a/src/packages/members/member/workspace/member/manifests.ts +++ b/src/packages/members/member/workspace/member/manifests.ts @@ -4,9 +4,8 @@ import type { ManifestWorkspaceActions, ManifestWorkspaceView, } from '@umbraco-cms/backoffice/workspace'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_CONTENT_HAS_PROPERTIES_WORKSPACE_CONDITION } from '@umbraco-cms/backoffice/content'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_MEMBER_WORKSPACE_ALIAS = 'Umb.Workspace.Member'; @@ -48,7 +47,7 @@ export const workspaceViews: Array = [ kind: 'contentEditor', alias: 'Umb.WorkspaceView.Member.Content', name: 'Member Workspace Content View', - weight: 100, + weight: 1000, meta: { label: '#general_details', pathname: 'content', @@ -69,10 +68,10 @@ export const workspaceViews: Array = [ alias: 'Umb.WorkspaceView.Member.Member', name: 'Member Workspace Member View', js: () => import('./views/member/member-workspace-view-member.element.js'), - weight: 200, + weight: 500, meta: { - label: '#treeHeaders_member', - pathname: 'member', + label: '#apps_umbInfo', + pathname: 'info', icon: 'icon-user', }, conditions: [ diff --git a/src/packages/members/member/workspace/member/member-workspace.context.ts b/src/packages/members/member/workspace/member/member-workspace.context.ts index 0c54e85606..d21e65c1f2 100644 --- a/src/packages/members/member/workspace/member/member-workspace.context.ts +++ b/src/packages/members/member/workspace/member/member-workspace.context.ts @@ -1,186 +1,47 @@ -import { UmbMemberDetailRepository } from '../../repository/index.js'; -import type { - UmbMemberDetailModel, - UmbMemberValueModel, - UmbMemberVariantModel, - UmbMemberVariantOptionModel, -} from '../../types.js'; +import type { UmbMemberDetailRepository } from '../../repository/index.js'; +import { UMB_MEMBER_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js'; +import type { UmbMemberDetailModel, UmbMemberVariantModel } from '../../types.js'; import { UmbMemberPropertyDatasetContext } from '../../property-dataset-context/member-property-dataset-context.js'; import { UMB_MEMBER_ENTITY_TYPE, UMB_MEMBER_ROOT_ENTITY_TYPE } from '../../entity.js'; -import { sortVariants } from '../../utils.js'; -import { UMB_MEMBER_MANAGEMENT_SECTION_PATH } from '../../../section/index.js'; import { UMB_MEMBER_WORKSPACE_ALIAS } from './manifests.js'; import { UmbMemberWorkspaceEditorElement } from './member-workspace-editor.element.js'; import { UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD } from './constants.js'; import { UmbMemberTypeDetailRepository, type UmbMemberTypeDetailModel } from '@umbraco-cms/backoffice/member-type'; import { - UmbSubmittableWorkspaceContextBase, UmbWorkspaceIsNewRedirectController, UmbWorkspaceIsNewRedirectControllerAlias, - UmbWorkspaceSplitViewManager, } from '@umbraco-cms/backoffice/workspace'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { - UmbArrayState, - UmbObjectState, - appendToFrozenArray, - mergeObservables, -} from '@umbraco-cms/backoffice/observable-api'; -import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; -import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; -import type { UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository'; -import { UmbContentWorkspaceDataManager, type UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; -import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; -import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; -import { map } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; -import { - UmbRequestReloadChildrenOfEntityEvent, - UmbRequestReloadStructureForEntityEvent, -} from '@umbraco-cms/backoffice/entity-action'; -import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbContentDetailWorkspaceContextBase, type UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; + +type ContentModel = UmbMemberDetailModel; +type ContentTypeModel = UmbMemberTypeDetailModel; -type EntityModel = UmbMemberDetailModel; export class UmbMemberWorkspaceContext - extends UmbSubmittableWorkspaceContextBase - implements UmbContentWorkspaceContext + extends UmbContentDetailWorkspaceContextBase< + ContentModel, + UmbMemberDetailRepository, + ContentTypeModel, + UmbMemberVariantModel + > + implements UmbContentWorkspaceContext { - public readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const; - - public readonly repository = new UmbMemberDetailRepository(this); - - #parent = new UmbObjectState({ entityType: UMB_MEMBER_ROOT_ENTITY_TYPE, unique: null }); - readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined)); - readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined)); - - readonly #data = new UmbContentWorkspaceDataManager(this, UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD); - #getDataPromise?: Promise>; - - // TODO: Optimize this so it uses either a App Language Context or another somehow cached solution? [NL] - #languageRepository = new UmbLanguageCollectionRepository(this); - #languages = new UmbArrayState([], (x) => x.unique); - public readonly languages = this.#languages.asObservable(); - - public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); - - public isLoaded() { - return this.#getDataPromise; - } - - readonly data = this.#data.current; - readonly unique = this.#data.createObservablePartOfCurrent((data) => data?.unique); - readonly createDate = this.#data.createObservablePartOfCurrent((data) => data?.variants[0].createDate); - readonly updateDate = this.#data.createObservablePartOfCurrent((data) => data?.variants[0].updateDate); - readonly contentTypeUnique = this.#data.createObservablePartOfCurrent((data) => data?.memberType.unique); - - readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants ?? []); - - readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); - getValues() { - return this.#data.getCurrent()?.values; - } - - readonly structure = new UmbContentTypeStructureManager(this, new UmbMemberTypeDetailRepository(this)); - readonly variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); - readonly variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); - readonly varies = this.structure.ownerContentTypeObservablePart((x) => - x ? x.variesByCulture || x.variesBySegment : undefined, - ); - #varies?: boolean; - #variesByCulture?: boolean; - #variesBySegment?: boolean; - - readonly kind = this.#data.createObservablePartOfCurrent((data) => data?.kind); - - readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); - #dataTypeSchemaAliasMap = new Map(); - - readonly splitView = new UmbWorkspaceSplitViewManager(); - - readonly variantOptions = mergeObservables( - [this.varies, this.variants, this.languages], - ([varies, variants, languages]) => { - // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL] - if (varies === true) { - return languages.map((language) => { - return { - variant: variants.find((x) => x.culture === language.unique), - language, - // TODO: When including segments, this object should be updated to include a object for the segment. [NL] - // TODO: When including segments, the unique should be updated to include the segment as well. [NL] - unique: language.unique, // This must be a variantId string! - culture: language.unique, - segment: null, - } as UmbMemberVariantOptionModel; - }); - } else if (varies === false) { - return [ - { - variant: variants.find((x) => x.culture === null), - language: languages.find((x) => x.isDefault), - culture: null, - segment: null, - unique: UMB_INVARIANT_CULTURE, // This must be a variantId string! - } as UmbMemberVariantOptionModel, - ]; - } - return [] as Array; - }, - ).pipe(map((results) => results.sort(sortVariants))); - - // TODO: this should be set up for all entity workspace contexts in a base class - #entityContext = new UmbEntityContext(this); + readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.memberType.unique); + readonly kind = this._data.createObservablePartOfCurrent((data) => data?.kind); + readonly createDate = this._data.createObservablePartOfCurrent((data) => data?.variants[0].createDate); + readonly updateDate = this._data.createObservablePartOfCurrent((data) => data?.variants[0].updateDate); constructor(host: UmbControllerHost) { - super(host, UMB_MEMBER_WORKSPACE_ALIAS); + super(host, { + entityType: UMB_MEMBER_ENTITY_TYPE, + workspaceAlias: UMB_MEMBER_WORKSPACE_ALIAS, + detailRepositoryAlias: UMB_MEMBER_DETAIL_REPOSITORY_ALIAS, + contentTypeDetailRepository: UmbMemberTypeDetailRepository, + contentVariantScaffold: UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD, + }); this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); - this.observe( - this.varies, - (varies) => { - this.#data.setVaries(varies); - this.#varies = varies; - }, - null, - ); - this.observe( - this.variesByCulture, - (varies) => { - this.#data.setVariesByCulture(varies); - this.#variesByCulture = varies; - }, - null, - ); - this.observe( - this.variesBySegment, - (varies) => { - this.#data.setVariesBySegment(varies); - this.#variesBySegment = varies; - }, - null, - ); - this.observe( - this.structure.contentTypeDataTypeUniques, - (dataTypeUniques: Array) => { - this.#dataTypeItemManager.setUniques(dataTypeUniques); - }, - null, - ); - this.observe( - this.#dataTypeItemManager.items, - (dataTypes) => { - // Make a map of the data type unique and editorAlias: - this.#dataTypeSchemaAliasMap = new Map( - dataTypes.map((dataType) => { - return [dataType.unique, dataType.propertyEditorSchemaAlias]; - }), - ); - }, - null, - ); - this.loadLanguages(); this.routes.setRoutes([ { @@ -188,7 +49,7 @@ export class UmbMemberWorkspaceContext component: () => new UmbMemberWorkspaceEditorElement(), setup: async (_component, info) => { const memberTypeUnique = info.match.params.memberTypeUnique; - this.create(memberTypeUnique); + await this.create(memberTypeUnique); new UmbWorkspaceIsNewRedirectController( this, @@ -209,241 +70,34 @@ export class UmbMemberWorkspaceContext ]); } - override resetState() { - super.resetState(); - this.#data.clear(); - } - - async loadLanguages() { - // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] - const { data } = await this.#languageRepository.requestCollection({}); - this.#languages.setValue(data?.items ?? []); - } - - async load(unique: string) { - this.resetState(); - this.#getDataPromise = this.repository.requestByUnique(unique); - type GetDataType = Awaited>; - const { data, asObservable } = (await this.#getDataPromise) as GetDataType; - - if (data) { - this.#entityContext.setEntityType(UMB_MEMBER_ENTITY_TYPE); - this.#entityContext.setUnique(unique); - this.setIsNew(false); - this.#data.setPersisted(data); - this.#data.setCurrent(data); - } - - this.observe(asObservable(), (member) => this.#onMemberStoreChange(member), 'umbMemberStoreObserver'); - } - - #onMemberStoreChange(member: EntityModel | undefined) { - if (!member) { - //TODO: This solution is alright for now. But reconsider when we introduce signal-r - history.pushState(null, '', UMB_MEMBER_MANAGEMENT_SECTION_PATH); - } - } - async create(memberTypeUnique: string) { - this.resetState(); - this.#getDataPromise = this.repository.createScaffold({ - memberType: { - unique: memberTypeUnique, + return this.createScaffold({ + parent: { entityType: UMB_MEMBER_ROOT_ENTITY_TYPE, unique: null }, + preset: { + memberType: { + unique: memberTypeUnique, + }, }, }); - const { data } = await this.#getDataPromise; - if (!data) return undefined; - - this.#entityContext.setEntityType(UMB_MEMBER_ENTITY_TYPE); - this.#entityContext.setUnique(data.unique); - this.setIsNew(true); - this.#data.setPersisted(undefined); - this.#data.setCurrent(data); - return data; - } - - getData() { - return this.#data.getCurrent(); - } - - getUnique() { - return this.getData()?.unique; - } - - getEntityType() { - return UMB_MEMBER_ENTITY_TYPE; - } - - getContentTypeId() { - return this.getData()?.memberType.unique; - } - - getVaries() { - return this.#varies; - } - getVariesByCulture() { - return this.#variesByCulture; - } - getVariesBySegment() { - return this.#variesBySegment; - } - - variantById(variantId: UmbVariantId) { - return this.#data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x))); - } - - getVariant(variantId: UmbVariantId) { - return this.#data.getCurrent()?.variants?.find((x) => variantId.compare(x)); - } - - getName(variantId?: UmbVariantId) { - const variants = this.#data.getCurrent()?.variants; - if (!variants) return; - if (variantId) { - return variants.find((x) => variantId.compare(x))?.name; - } else { - return variants[0]?.name; - } - } - - setName(name: string, variantId?: UmbVariantId) { - this.#data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); - } - - name(variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '', - ); - } - - async propertyStructureById(propertyId: string) { - return this.structure.propertyStructureById(propertyId); } /** - * @function propertyValueByAlias - * @param {string} propertyAlias - property alias to observe - * @param {UmbVariantId} variantId - variant identifier for the value to observe - * @returns {Promise | undefined>} - * @description Get an Observable for the value of this property. + * Gets the unique identifier of the content type. + * @deprecated Use `getContentTypeUnique` instead. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbMemberWorkspaceContext */ - async propertyValueByAlias(propertyAlias: string, variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => - data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true)) - ?.value as PropertyValueType, - ); + getContentTypeId(): string | undefined { + return this.getContentTypeUnique(); } /** - * Get the current value of the property with the given alias and variantId. - * @param {string} alias - property alias to set. - * @param {UmbVariantId} variantId - variant identifier for this value to be defined for. - * @returns {ReturnType | undefined}The value or undefined if not set or found. + * Gets the unique identifier of the content type. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbMemberWorkspaceContext */ - getPropertyValue(alias: string, variantId?: UmbVariantId) { - const currentData = this.getData(); - if (currentData) { - const newDataSet = currentData.values?.find( - (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), - ); - return newDataSet?.value as ReturnType; - } - return undefined; - } - async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { - this.initiatePropertyValueChange(); - variantId ??= UmbVariantId.CreateInvariant(); - const property = await this.structure.getPropertyStructureByAlias(alias); - - if (!property) { - throw new Error(`Property alias "${alias}" not found.`); - } - - const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); - if (!editorAlias) { - throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); - } - - const entry = { ...variantId.toObject(), alias, editorAlias, value } as UmbMemberValueModel; - - const currentData = this.getData(); - if (currentData) { - const values = appendToFrozenArray( - currentData.values ?? [], - entry, - (x) => x.alias === alias && variantId!.compare(x), - ); - this.#data.updateCurrent({ values }); - - // TODO: We should move this type of logic to the act of saving [NL] - this.#data.ensureVariantData(variantId); - } - this.finishPropertyValueChange(); - } - - initiatePropertyValueChange() { - this.#data.initiatePropertyValueChange(); - } - finishPropertyValueChange = () => { - this.#data.finishPropertyValueChange(); - }; - - async #handleSave() { - const current = this.#data.getCurrent(); - if (!current) throw new Error('Data is missing'); - if (!current.unique) throw new Error('Unique is missing'); - - if (this.getIsNew()) { - // Create: - const parent = this.#parent.getValue(); - if (!parent) throw new Error('Parent is not set'); - - const { data, error } = await this.repository.create(current); - if (!data || error) { - throw new Error('Could not create member.'); - } - - this.setIsNew(false); - this.#data.setPersisted(data); - // TODO: Missing variant data filtering. - this.#data.setCurrent(data); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadChildrenOfEntityEvent({ - entityType: parent.entityType, - unique: parent.unique, - }); - eventContext.dispatchEvent(event); - } else { - // Save: - const { data, error } = await this.repository.save(current); - if (!data || error) { - throw new Error('Could not update member.'); - } - this.#data.setPersisted(data); - // TODO: Missing variant data filtering. - this.#data.setCurrent(data); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - entityType: this.getEntityType(), - unique: this.getUnique()!, - }); - - eventContext.dispatchEvent(event); - } - } - - async submit() { - return this.#handleSave(); - } - - async delete() { - const id = this.getUnique(); - if (id) { - await this.repository.delete(id); - } + getContentTypeUnique(): string | undefined { + return this.getData()?.memberType.unique; } public createPropertyDatasetContext( @@ -453,22 +107,18 @@ export class UmbMemberWorkspaceContext return new UmbMemberPropertyDatasetContext(host, this, variantId); } - public override destroy(): void { - super.destroy(); - } - set( propertyName: PropertyName, value: UmbMemberDetailModel[PropertyName], ) { - this.#data.updateCurrent({ [propertyName]: value }); + this._data.updateCurrent({ [propertyName]: value }); } // Only for CRUD demonstration purposes - updateData(data: Partial) { - const currentData = this.#data.getCurrent(); + updateData(data: Partial) { + const currentData = this._data.getCurrent(); if (!currentData) throw new Error('No data to update'); - this.#data.setCurrent({ ...currentData, ...data }); + this._data.setCurrent({ ...currentData, ...data }); } get email(): string { @@ -512,7 +162,7 @@ export class UmbMemberWorkspaceContext } #get(propertyName: PropertyName) { - return this.#data.getCurrent()?.[propertyName]; + return this._data.getCurrent()?.[propertyName]; } } diff --git a/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts b/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts index 39c8a87112..4da0e7161a 100644 --- a/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts +++ b/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts @@ -5,13 +5,14 @@ import type { UmbLinkPickerModalValue, } from './link-picker-modal.token.js'; import { css, customElement, html, nothing, query, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { isUmbracoFolder, UmbMediaTypeStructureRepository } from '@umbraco-cms/backoffice/media-type'; +import { umbFocus } from '@umbraco-cms/backoffice/lit-element'; import { UmbDocumentDetailRepository } from '@umbraco-cms/backoffice/document'; import { UmbMediaDetailRepository } from '@umbraco-cms/backoffice/media'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import type { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document'; import type { UmbInputMediaElement } from '@umbraco-cms/backoffice/media'; import type { UUIBooleanInputEvent, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; -import { isUmbracoFolder, UmbMediaTypeStructureRepository } from '@umbraco-cms/backoffice/media-type'; type UmbInputPickerEvent = CustomEvent & { target: { value?: string } }; @@ -56,8 +57,10 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement
    @@ -169,7 +173,8 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement + @change=${this.#onLinkUrlInput} + ${umbFocus()}> ${when( diff --git a/src/packages/property-editors/collection/manifests.ts b/src/packages/property-editors/collection/manifests.ts index ae597d3610..eb32c743c8 100644 --- a/src/packages/property-editors/collection/manifests.ts +++ b/src/packages/property-editors/collection/manifests.ts @@ -50,19 +50,19 @@ const propertyEditorUiManifest: ManifestPropertyEditorUi = { { alias: 'icon', label: 'Workspace View icon', - description: 'The icon for the Collection\'s Workspace View.', + description: "The icon for the Collection's Workspace View.", propertyEditorUiAlias: 'Umb.PropertyEditorUi.IconPicker', }, { alias: 'tabName', label: 'Workspace View name', - description: 'The name of the Collection\'s Workspace View (default if empty: Child Items).', + description: "The name of the Collection's Workspace View (default if empty: Child Items).", propertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox', }, { alias: 'showContentFirst', label: 'Show Content Workspace View First', - description: 'Enable this to show the Content Workspace View by default instead of the Collection\'s.', + description: "Enable this to show the Content Workspace View by default instead of the Collection's.", propertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle', }, ], diff --git a/src/packages/templating/partial-views/menu/manifests.ts b/src/packages/templating/partial-views/menu/manifests.ts index de2147b6f0..414875d9d6 100644 --- a/src/packages/templating/partial-views/menu/manifests.ts +++ b/src/packages/templating/partial-views/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_PARTIAL_VIEW_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/templating/partial-views/workspace/manifests.ts b/src/packages/templating/partial-views/workspace/manifests.ts index 7e21cc0506..4ab987402a 100644 --- a/src/packages/templating/partial-views/workspace/manifests.ts +++ b/src/packages/templating/partial-views/workspace/manifests.ts @@ -1,5 +1,4 @@ -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_PARTIAL_VIEW_WORKSPACE_ALIAS = 'Umb.Workspace.PartialView'; diff --git a/src/packages/templating/scripts/menu/manifests.ts b/src/packages/templating/scripts/menu/manifests.ts index 9dd63daaa8..f4e55324a8 100644 --- a/src/packages/templating/scripts/menu/manifests.ts +++ b/src/packages/templating/scripts/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_SCRIPT_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_SCRIPT_MENU_ITEM_ALIAS = 'Umb.MenuItem.Script'; export const manifests: Array = [ diff --git a/src/packages/templating/scripts/workspace/manifests.ts b/src/packages/templating/scripts/workspace/manifests.ts index 13664d8a9c..ad7af55c21 100644 --- a/src/packages/templating/scripts/workspace/manifests.ts +++ b/src/packages/templating/scripts/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_SCRIPT_ENTITY_TYPE } from '../entity.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const UMB_SCRIPT_WORKSPACE_ALIAS = 'Umb.Workspace.Script'; export const UMB_SAVE_SCRIPT_WORKSPACE_ACTION_ALIAS = 'Umb.WorkspaceAction.Script.Save'; diff --git a/src/packages/templating/scripts/workspace/script-workspace.context.ts b/src/packages/templating/scripts/workspace/script-workspace.context.ts index fd116f0efe..4bdc50a90a 100644 --- a/src/packages/templating/scripts/workspace/script-workspace.context.ts +++ b/src/packages/templating/scripts/workspace/script-workspace.context.ts @@ -33,7 +33,7 @@ export class UmbScriptWorkspaceContext setup: async (component: PageComponent, info: IRoutingInfo) => { const parentEntityType = info.match.params.entityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; - this.createScaffold({ parent: { entityType: parentEntityType, unique: parentUnique } }); + await this.createScaffold({ parent: { entityType: parentEntityType, unique: parentUnique } }); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/templating/stylesheets/menu/manifests.ts b/src/packages/templating/stylesheets/menu/manifests.ts index 1a74c5f0f1..1b93c439a8 100644 --- a/src/packages/templating/stylesheets/menu/manifests.ts +++ b/src/packages/templating/stylesheets/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_STYLESHEET_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/templating/stylesheets/workspace/manifests.ts b/src/packages/templating/stylesheets/workspace/manifests.ts index 98911276c1..ee593b786d 100644 --- a/src/packages/templating/stylesheets/workspace/manifests.ts +++ b/src/packages/templating/stylesheets/workspace/manifests.ts @@ -1,5 +1,4 @@ -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_STYLESHEET_WORKSPACE_ALIAS = 'Umb.Workspace.Stylesheet'; diff --git a/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts b/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts index d68dc8824c..118981c9f1 100644 --- a/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts +++ b/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts @@ -45,7 +45,7 @@ export class UmbStylesheetWorkspaceContext setup: async (component: PageComponent, info: IRoutingInfo) => { const parentEntityType = info.match.params.entityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; - this.create({ entityType: parentEntityType, unique: parentUnique }); + await this.create({ entityType: parentEntityType, unique: parentUnique }); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/templating/templates/menu/manifests.ts b/src/packages/templating/templates/menu/manifests.ts index ab40f89560..3c63d3fe2b 100644 --- a/src/packages/templating/templates/menu/manifests.ts +++ b/src/packages/templating/templates/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_TEMPLATE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/templating/templates/workspace/manifests.ts b/src/packages/templating/templates/workspace/manifests.ts index fc2b33e8ce..8f9c84b800 100644 --- a/src/packages/templating/templates/workspace/manifests.ts +++ b/src/packages/templating/templates/workspace/manifests.ts @@ -1,5 +1,4 @@ -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_TEMPLATE_WORKSPACE_ALIAS = 'Umb.Workspace.Template'; diff --git a/src/packages/templating/templates/workspace/template-workspace.context.ts b/src/packages/templating/templates/workspace/template-workspace.context.ts index 86a68407b4..6636e43d5f 100644 --- a/src/packages/templating/templates/workspace/template-workspace.context.ts +++ b/src/packages/templating/templates/workspace/template-workspace.context.ts @@ -41,10 +41,10 @@ export class UmbTemplateWorkspaceContext { path: 'create/parent/:entityType/:parentUnique', component: UmbTemplateWorkspaceEditorElement, - setup: (component: PageComponent, info: IRoutingInfo) => { + setup: async (component: PageComponent, info: IRoutingInfo) => { const parentEntityType = info.match.params.entityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; - this.create({ entityType: parentEntityType, unique: parentUnique }); + await this.create({ entityType: parentEntityType, unique: parentUnique }); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.defaults.ts b/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.defaults.ts index 4526ca07d5..ac1260b8ce 100644 --- a/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.defaults.ts +++ b/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.defaults.ts @@ -1,4 +1,4 @@ -import { UMB_CONTENT_REQUEST_EVENT_TYPE, type UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api'; +import { UMB_CONTEXT_REQUEST_EVENT_TYPE, type UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api'; import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; import { UUIIconRequestEvent } from '@umbraco-cms/backoffice/external/uui'; @@ -34,7 +34,7 @@ export const defaultFallbackConfig: RawEditorOptions = { init_instance_callback: function (editor) { // The following code is the context api proxy. [NL] // It re-dispatches the context api request event to the origin target of this modal, in other words the element that initiated the modal. [NL] - editor.dom.doc.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => { + editor.dom.doc.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => { if (!editor.iframeElement) return; event.stopImmediatePropagation(); diff --git a/src/packages/ufm/components/content-name/content-name.component.ts b/src/packages/ufm/components/content-name/content-name.component.ts index 49a84361e7..460befde95 100644 --- a/src/packages/ufm/components/content-name/content-name.component.ts +++ b/src/packages/ufm/components/content-name/content-name.component.ts @@ -9,8 +9,8 @@ export class UmbUfmContentNameComponent extends UmbUfmComponentBase { if (token.prefix === '~') { /* - * @deprecated since version 15.0-rc3 - */ + * @deprecated since version 15.0-rc3 + */ console.warn(`Please update your UFM syntax from \`${token.raw}\` to \`{umbContentName:${token.text}}\`.`); } diff --git a/src/packages/user/change-password/modal/change-password-modal.element.ts b/src/packages/user/change-password/modal/change-password-modal.element.ts index bdff723460..d0fcbf4afd 100644 --- a/src/packages/user/change-password/modal/change-password-modal.element.ts +++ b/src/packages/user/change-password/modal/change-password-modal.element.ts @@ -1,18 +1,24 @@ import type { UmbChangePasswordModalData, UmbChangePasswordModalValue } from './change-password-modal.token.js'; -import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user'; -import { UmbUserItemRepository } from '@umbraco-cms/backoffice/user'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit'; -import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, query, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbUserItemRepository } from '@umbraco-cms/backoffice/user'; +import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user'; +import type { UUIInputPasswordElement } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-change-password-modal') export class UmbChangePasswordModalElement extends UmbModalBaseElement< UmbChangePasswordModalData, UmbChangePasswordModalValue > { + @query('#newPassword') + private _newPasswordInput?: UUIInputPasswordElement; + + @query('#confirmPassword') + private _confirmPasswordInput?: UUIInputPasswordElement; + @state() - private _headline: string = 'Change password'; + private _headline: string = this.localize.term('general_changePassword'); @state() private _isCurrentUser: boolean = false; @@ -35,10 +41,8 @@ export class UmbChangePasswordModalElement extends UmbModalBaseElement< const formData = new FormData(form); - // TODO: validate that the new password and confirm password match const oldPassword = formData.get('oldPassword') as string; const newPassword = formData.get('newPassword') as string; - //const confirmPassword = formData.get('confirmPassword') as string; this.value = { oldPassword, newPassword }; this.modalContext?.submit(); @@ -54,12 +58,7 @@ export class UmbChangePasswordModalElement extends UmbModalBaseElement< } async #setIsCurrentUser() { - if (!this.data?.user.unique) { - this._isCurrentUser = false; - return; - } - - if (!this.#currentUserContext) { + if (!this.#currentUserContext || !this.data?.user.unique) { this._isCurrentUser = false; return; } @@ -67,7 +66,13 @@ export class UmbChangePasswordModalElement extends UmbModalBaseElement< this._isCurrentUser = await this.#currentUserContext.isUserCurrentUser(this.data.user.unique); } - protected override async firstUpdated(): Promise { + protected override async firstUpdated() { + this._confirmPasswordInput?.addValidator( + 'customError', + () => this.localize.term('user_passwordMismatch'), + () => this._confirmPasswordInput?.value !== this._newPasswordInput?.value, + ); + if (!this.data?.user.unique) return; const { data } = await this.#userItemRepository.requestItems([this.data.user.unique]); @@ -81,55 +86,60 @@ export class UmbChangePasswordModalElement extends UmbModalBaseElement< return html` -
    - ${this._isCurrentUser ? this.#renderOldPasswordInput() : nothing} + + ${when( + this._isCurrentUser, + () => html` + + + Current password + + + + + `, + )} - New password + + New password + + required-message="New password is required"> + - Confirm password + + Confirm new password + + required-message="Confirm password is required"> +
    - - + + look="primary" + label=${this.localize.term('general_confirm')}>
    `; } - #renderOldPasswordInput() { - return html` - - Old password - - - `; - } - - static override styles: CSSResultGroup = [ + static override readonly styles = [ UmbTextStyles, css` uui-input-password { diff --git a/src/packages/user/change-password/modal/manifests.ts b/src/packages/user/change-password/modal/manifests.ts index f08d08322a..b3e3f0c901 100644 --- a/src/packages/user/change-password/modal/manifests.ts +++ b/src/packages/user/change-password/modal/manifests.ts @@ -3,6 +3,6 @@ export const manifests: Array = [ type: 'modal', alias: 'Umb.Modal.ChangePassword', name: 'Change Password Modal', - js: () => import('./change-password-modal.element.js'), + element: () => import('./change-password-modal.element.js'), }, ]; diff --git a/src/packages/user/current-user/current-user.context.ts b/src/packages/user/current-user/current-user.context.ts index b6836e70b7..d31467cce3 100644 --- a/src/packages/user/current-user/current-user.context.ts +++ b/src/packages/user/current-user/current-user.context.ts @@ -11,6 +11,7 @@ import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registr import { UMB_SECTION_PATH_PATTERN } from '@umbraco-cms/backoffice/section'; import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; import { ensurePathEndsWithSlash } from '@umbraco-cms/backoffice/utils'; +import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; export class UmbCurrentUserContext extends UmbContextBase { #currentUser = new UmbObjectState(undefined); @@ -83,6 +84,142 @@ export class UmbCurrentUserContext extends UmbContextBase return currentUser?.isAdmin ?? false; } + /** + * Get the allowed sections for the current user + * @returns {Array | undefined} The allowed sections for the current user + */ + getAllowedSection(): Array | undefined { + return this.#currentUser.getValue()?.allowedSections; + } + + /** + * Get the avatar urls for the current user + * @returns {Array | undefined} The avatar urls for the current user + */ + getAvatarUrls(): Array | undefined { + return this.#currentUser.getValue()?.avatarUrls; + } + + /** + * Get the document start node uniques for the current user + * @returns {Array | undefined} The document start node uniques for the current user + */ + getDocumentStartNodeUniques(): Array | undefined { + return this.#currentUser.getValue()?.documentStartNodeUniques; + } + + /** + * Get the email for the current user + * @returns {string | undefined} The email for the current user + */ + getEmail(): string | undefined { + return this.#currentUser.getValue()?.email; + } + + /** + * Get the fallback permissions for the current user + * @returns {Array | undefined} The fallback permissions for the current user + */ + getFallbackPermissions(): Array | undefined { + return this.#currentUser.getValue()?.fallbackPermissions; + } + + /** + * Get if the current user has access to all languages + * @returns {boolean | undefined} True if the current user has access to all languages, otherwise false + */ + getHasAccessToAllLanguages(): boolean | undefined { + return this.#currentUser.getValue()?.hasAccessToAllLanguages; + } + + /** + * Get if the current user has access to sensitive data + * @returns {boolean | undefined} True if the current user has access to sensitive data, otherwise false + */ + getHasAccessToSensitiveData(): boolean | undefined { + return this.#currentUser.getValue()?.hasAccessToSensitiveData; + } + + /** + * Get if the current user has document root access + * @returns {boolean | undefined} True if the current user has document root access, otherwise false + */ + getHasDocumentRootAccess(): boolean | undefined { + return this.#currentUser.getValue()?.hasDocumentRootAccess; + } + + /** + * Get if the current user has media root access + * @returns {boolean | undefined} True if the current user has media root access, otherwise false + */ + getHasMediaRootAccess(): boolean | undefined { + return this.#currentUser.getValue()?.hasMediaRootAccess; + } + + /** + * Get if the current user is an admin + * @returns {boolean | undefined} True if the current user is an admin, otherwise false + */ + getIsAdmin(): boolean | undefined { + return this.#currentUser.getValue()?.isAdmin; + } + + /** + * Get the language iso code for the current user + * @returns {string | undefined} The language iso code for the current user + */ + getLanguageIsoCode(): string | undefined { + return this.#currentUser.getValue()?.languageIsoCode; + } + + /** + * Get the languages for the current user + * @returns {Array | undefined} The languages for the current user + */ + getLanguages(): Array | undefined { + return this.#currentUser.getValue()?.languages; + } + + /** + * Get the media start node uniques for the current user + * @returns {Array | undefined} The media start node uniques for the current user + */ + getMediaStartNodeUniques(): Array | undefined { + return this.#currentUser.getValue()?.mediaStartNodeUniques; + } + + /** + * Get the name for the current user + * @returns {string | undefined} The name for the current user + */ + getName(): string | undefined { + return this.#currentUser.getValue()?.name; + } + + /** + * Get the permissions for the current user + * @returns {Array | undefined} The permissions for the current user + */ + getPermissions() { + return this.#currentUser.getValue()?.permissions; + } + + /** + * Get the unique for the current user + * @returns {string | undefined} The unique for the current user + */ + getUnique(): string | undefined { + return this.#currentUser.getValue()?.unique; + } + + /** + * Get the user name for the current user + * @returns {string | undefined} The user name for the current user + */ + getUserName(): string | undefined { + return this.#currentUser.getValue()?.userName; + } + #observeIsAuthorized() { if (!this.#authContext) return; this.observe(this.#authContext.isAuthorized, (isAuthorized) => { diff --git a/src/packages/user/user-group/collection/views/user-group-table-collection-view.element.ts b/src/packages/user/user-group/collection/views/user-group-table-collection-view.element.ts index f94107c739..fd9ea76a2b 100644 --- a/src/packages/user/user-group/collection/views/user-group-table-collection-view.element.ts +++ b/src/packages/user/user-group/collection/views/user-group-table-collection-view.element.ts @@ -47,6 +47,10 @@ export class UmbUserGroupCollectionTableViewElement extends UmbLitElement { name: this.localize.term('user_mediastartnode'), alias: 'userGroupMediaStartNode', }, + { + name: '', + alias: 'entityActions', + }, ]; @state() @@ -122,6 +126,14 @@ export class UmbUserGroupCollectionTableViewElement extends UmbLitElement { ? this.#mediaStartNodeMap.get(userGroup.mediaStartNode.unique) : this.localize.term('media_mediaRoot'), }, + { + columnAlias: 'entityActions', + value: html``, + }, ], }; }); diff --git a/src/packages/user/user-group/workspace/user-group-root/manifests.ts b/src/packages/user/user-group/workspace/user-group-root/manifests.ts index 0d528f9fc7..6291ed199a 100644 --- a/src/packages/user/user-group/workspace/user-group-root/manifests.ts +++ b/src/packages/user/user-group/workspace/user-group-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_USER_GROUP_COLLECTION_ALIAS } from '../../collection/index.js'; import { UMB_USER_GROUP_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_USER_GROUP_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/user/user-group/workspace/user-group/manifests.ts b/src/packages/user/user-group/workspace/user-group/manifests.ts index 3a2e096501..4738e15e07 100644 --- a/src/packages/user/user-group/workspace/user-group/manifests.ts +++ b/src/packages/user/user-group/workspace/user-group/manifests.ts @@ -1,5 +1,4 @@ -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_USER_GROUP_WORKSPACE_ALIAS = 'Umb.Workspace.UserGroup'; diff --git a/src/packages/user/user-group/workspace/user-group/user-group-workspace.context.ts b/src/packages/user/user-group/workspace/user-group/user-group-workspace.context.ts index 7650f9daa5..0e2d78241b 100644 --- a/src/packages/user/user-group/workspace/user-group/user-group-workspace.context.ts +++ b/src/packages/user/user-group/workspace/user-group/user-group-workspace.context.ts @@ -42,8 +42,8 @@ export class UmbUserGroupWorkspaceContext { path: 'create', component: UmbUserGroupWorkspaceEditorElement, - setup: () => { - this.createScaffold({ parent: { entityType: UMB_USER_GROUP_ROOT_ENTITY_TYPE, unique: null } }); + setup: async () => { + await this.createScaffold({ parent: { entityType: UMB_USER_GROUP_ROOT_ENTITY_TYPE, unique: null } }); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/packages/user/user/collection/views/table/user-table-collection-view.element.ts b/src/packages/user/user/collection/views/table/user-table-collection-view.element.ts index 6efabcb53a..8893980225 100644 --- a/src/packages/user/user/collection/views/table/user-table-collection-view.element.ts +++ b/src/packages/user/user/collection/views/table/user-table-collection-view.element.ts @@ -48,6 +48,10 @@ export class UmbUserTableCollectionViewElement extends UmbLitElement { alias: 'userStatus', elementName: 'umb-user-table-status-column-layout', }, + { + name: '', + alias: 'entityActions', + }, ]; @state() @@ -140,6 +144,14 @@ export class UmbUserTableCollectionViewElement extends UmbLitElement { status: user.state, }, }, + { + columnAlias: 'entityActions', + value: html``, + }, ], }; }); diff --git a/src/packages/user/user/components/user-avatar/user-avatar.element.ts b/src/packages/user/user/components/user-avatar/user-avatar.element.ts index 44661991dc..84fe60e2da 100644 --- a/src/packages/user/user/components/user-avatar/user-avatar.element.ts +++ b/src/packages/user/user/components/user-avatar/user-avatar.element.ts @@ -117,8 +117,8 @@ export class UmbUserAvatarElement extends UmbLitElement { css` uui-avatar { background-color: transparent; - border: 1.5px solid var(--uui-color-border); color: inherit; + box-shadow: 0 0 0 1.5px var(--uui-color-border); } uui-avatar.has-image { diff --git a/src/packages/user/user/workspace/user-root/manifests.ts b/src/packages/user/user/workspace/user-root/manifests.ts index 44217e9ba9..a4e5970e31 100644 --- a/src/packages/user/user/workspace/user-root/manifests.ts +++ b/src/packages/user/user/workspace/user-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_USER_COLLECTION_ALIAS } from '../../collection/constants.js'; import { UMB_USER_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_USER_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/user/user/workspace/user/manifests.ts b/src/packages/user/user/workspace/user/manifests.ts index 1149050381..850cee09f9 100644 --- a/src/packages/user/user/workspace/user/manifests.ts +++ b/src/packages/user/user/workspace/user/manifests.ts @@ -1,7 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_USER_ENTITY_TYPE } from '../../entity.js'; import { UMB_USER_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/webhook/collection/views/table/column-layouts/boolean/webhook-table-boolean-column-layout.element.ts b/src/packages/webhook/collection/views/table/column-layouts/boolean/webhook-table-boolean-column-layout.element.ts deleted file mode 100644 index e16d4f4bb7..0000000000 --- a/src/packages/webhook/collection/views/table/column-layouts/boolean/webhook-table-boolean-column-layout.element.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; - -@customElement('umb-webhook-table-boolean-column-layout') -export class UmbWebhookTableBooleanColumnLayoutElement extends UmbLitElement { - @property({ attribute: false }) - value = false; - - override render() { - return html``; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-webhook-table-boolean-column-layout': UmbWebhookTableBooleanColumnLayoutElement; - } -} diff --git a/src/packages/webhook/collection/views/table/column-layouts/entity-actions/webhook-table-entity-actions-column-layout.element.ts b/src/packages/webhook/collection/views/table/column-layouts/entity-actions/webhook-table-entity-actions-column-layout.element.ts deleted file mode 100644 index 16cd56b978..0000000000 --- a/src/packages/webhook/collection/views/table/column-layouts/entity-actions/webhook-table-entity-actions-column-layout.element.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { UmbWebhookDetailModel } from '../../../../../types.js'; -import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; - -@customElement('umb-webhook-table-entity-actions-column-layout') -export class UmbWebhookTableEntityActionsColumnLayoutElement extends UmbLitElement { - @property({ attribute: false }) - value!: UmbWebhookDetailModel; - - @state() - _isOpen = false; - - #onActionExecuted() { - this._isOpen = false; - } - - override render() { - return html` - - - - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-webhook-table-entity-actions-column-layout': UmbWebhookTableEntityActionsColumnLayoutElement; - } -} diff --git a/src/packages/webhook/collection/views/table/webhook-table-collection-view.element.ts b/src/packages/webhook/collection/views/table/webhook-table-collection-view.element.ts index 4128f4e9d2..26e6566274 100644 --- a/src/packages/webhook/collection/views/table/webhook-table-collection-view.element.ts +++ b/src/packages/webhook/collection/views/table/webhook-table-collection-view.element.ts @@ -5,9 +5,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; import type { UmbTableColumn, UmbTableConfig, UmbTableItem } from '@umbraco-cms/backoffice/components'; -import './column-layouts/boolean/webhook-table-boolean-column-layout.element.js'; import './column-layouts/name/webhook-table-name-column-layout.element.js'; -import './column-layouts/entity-actions/webhook-table-entity-actions-column-layout.element.js'; import './column-layouts/content-type/webhook-table-name-column-layout.element.js'; @customElement('umb-webhook-table-collection-view') @@ -27,7 +25,6 @@ export class UmbWebhookTableCollectionViewElement extends UmbLitElement { { name: this.localize.term('webhooks_enabled'), alias: 'enabled', - elementName: 'umb-webhook-table-boolean-column-layout', }, { name: this.localize.term('webhooks_url'), @@ -45,7 +42,6 @@ export class UmbWebhookTableCollectionViewElement extends UmbLitElement { { name: '', alias: 'entityActions', - elementName: 'umb-webhook-table-entity-actions-column-layout', }, ]; @@ -85,7 +81,7 @@ export class UmbWebhookTableCollectionViewElement extends UmbLitElement { }, { columnAlias: 'enabled', - value: webhook.enabled, + value: html``, }, { columnAlias: 'events', @@ -97,7 +93,11 @@ export class UmbWebhookTableCollectionViewElement extends UmbLitElement { }, { columnAlias: 'entityActions', - value: webhook, + value: html``, }, ], }; diff --git a/src/packages/webhook/workspace/webhook-root/manifests.ts b/src/packages/webhook/workspace/webhook-root/manifests.ts index c837a613e0..4ce0a1b821 100644 --- a/src/packages/webhook/workspace/webhook-root/manifests.ts +++ b/src/packages/webhook/workspace/webhook-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_WEBHOOK_COLLECTION_ALIAS } from '../../collection/manifests.js'; import { UMB_WEBHOOK_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_WEBHOOK_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/webhook/workspace/webhook/manifests.ts b/src/packages/webhook/workspace/webhook/manifests.ts index ae338f664e..da58546d92 100644 --- a/src/packages/webhook/workspace/webhook/manifests.ts +++ b/src/packages/webhook/workspace/webhook/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_WEBHOOK_ENTITY_TYPE, UMB_WEBHOOK_WORKSPACE_ALIAS } from '../../entity.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/webhook/workspace/webhook/webhook-workspace.context.ts b/src/packages/webhook/workspace/webhook/webhook-workspace.context.ts index d817275122..67da34f9a9 100644 --- a/src/packages/webhook/workspace/webhook/webhook-workspace.context.ts +++ b/src/packages/webhook/workspace/webhook/webhook-workspace.context.ts @@ -28,7 +28,7 @@ export class UmbWebhookWorkspaceContext path: 'create', component: UmbWebhookWorkspaceEditorElement, setup: async () => { - this.createScaffold({ parent: { entityType: UMB_WEBHOOK_ROOT_ENTITY_TYPE, unique: null } }); + await this.createScaffold({ parent: { entityType: UMB_WEBHOOK_ROOT_ENTITY_TYPE, unique: null } }); new UmbWorkspaceIsNewRedirectController( this,