diff --git a/src/Umbraco.Web.UI.Client/.eslintignore b/src/Umbraco.Web.UI.Client/.eslintignore index d2d1d42353..2b14f644c9 100644 --- a/src/Umbraco.Web.UI.Client/.eslintignore +++ b/src/Umbraco.Web.UI.Client/.eslintignore @@ -1,5 +1,5 @@ .eslintrc.js -types +/types dist dist-cms schemas diff --git a/src/Umbraco.Web.UI.Client/.github/CONTRIBUTING.md b/src/Umbraco.Web.UI.Client/.github/CONTRIBUTING.md index e0b09f17a9..b870ebf151 100644 --- a/src/Umbraco.Web.UI.Client/.github/CONTRIBUTING.md +++ b/src/Umbraco.Web.UI.Client/.github/CONTRIBUTING.md @@ -9,7 +9,7 @@ Here is the LIT documentation and playground: [https://lit.dev](https://lit.dev) ### How best to find what needs converting from the old backoffice? 1. Navigate to [https://github.com/umbraco/Umbraco-CMS](https://github.com/umbraco/Umbraco-CMS) -2. Make sure you are on the `v13/dev` branch +2. Make sure you are on the `v14/dev` branch ### What is the process of contribution? @@ -35,12 +35,11 @@ The frontend has an API formatter that takes the OpenAPI schema file and convert ### Caveats 1. There is currently no way to add translations. All texts in the UI are expected to be written in Umbraco’s default language of English. -2. The backoffice can be run and tested against a real Umbraco instance by cloning down the `v13/dev` branch, but there are no guarantees about how well it works yet. -3. Authentication has not been built, so the login page is never shown - HQ is working actively on this. +2. The backoffice can be run and tested against a real Umbraco instance by cloning down the `v14/dev` branch, but there are no guarantees about how well it works yet. **Current schema for API:** -[https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v13/dev/src/Umbraco.Cms.Api.Management/OpenApi.json](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v13/dev/src/Umbraco.Cms.Api.Management/OpenApi.json) +[https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v13/dev/src/Umbraco.Cms.Api.Management/OpenApi.json](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v14/dev/src/Umbraco.Cms.Api.Management/OpenApi.json) **How to convert it:** @@ -73,8 +72,8 @@ The old AngularJS controllers will have to be converted into modern TypeScript a To make the first button work, which simply just requests a new status from the server, we must make a call to `PublishedCacheResource.getPublishedCacheStatus()`. An additional thing here is to wrap that in a friendly function called `tryExecuteAndNotify`, which is something we make available to developers to automatically handle the responses coming from the server and additionally use the Notifications to notify of any errors: ```typescript -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { PublishedCacheResource } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; +import { PublishedCacheResource } from '@umbraco-cms/backoffice/backend-api'; private _getStatus() { const { data: status } = await tryExecuteAndNotify(this, PublishedCacheResource.getPublishedCacheStatus()); @@ -113,17 +112,20 @@ To declare the Published Cache Status Dashboard as a new manifest, we need to ad ```typescript { - type: 'dashboard', - alias: 'Umb.Dashboard.PublishedStatus', - name: 'Published Status', - elementName: 'umb-dashboard-published-status', - loader: () => import('./backoffice/dashboards/published-status/dashboard-published-status.element'), - meta: { - sections: ['Umb.Section.Settings'], - pathname: 'published-status', - weight: 9, - }, -} + type: 'dashboard', + alias: 'Umb.Dashboard.PublishedStatus', + name: 'Published Status Dashboard', + elementName: 'umb-dashboard-published-status', + loader: () => import('./published-status/dashboard-published-status.element.js'), + weight: 200, + meta: { + label: 'Published Status', + pathname: 'published-status', + }, + conditions: { + sections: ['Umb.Section.Settings'], + }, +}, ``` Let’s go through each of these properties… @@ -150,7 +152,11 @@ Let’s go through each of these properties… - Loader: references a function call to import the file that the element is declared within -- Meta: allows us to reference additional data - in our case we can specify the section that our dashboard will sit within, the pathname that will be displayed in the url and the weight of the section +- Weight: allows us to specify the order in which the dashboard will be displayed within the tabs bar + +- Meta: allows us to reference additional data - in our case we can specify the label that is shown in the tabs bar and the pathname that will be displayed in the url + +- Conditions: allows us to specify the conditions that must be met in order for the dashboard to be displayed. In our case we are specifying that the dashboard will only be displayed within the Settings section ## API mock handlers @@ -164,7 +170,7 @@ So to define this, we must first add a handler for the Published Status called ` ```typescript const { rest } = window.MockServiceWorker; -import { umbracoPath } from '@umbraco-cms/utils'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; export const handlers = [ rest.get(umbracoPath('/published-cache/status'), (_req, res, ctx) => { @@ -226,10 +232,6 @@ We are using a tool called Web Test Runner which spins up a bunch of browsers us Working with playwright: [https://playwright.dev/docs/intro](https://playwright.dev/docs/intro) -### End-to-end testing - -This test is being performed by Playwright as well but is running in a mode where Playwright clicks through the browser in different scenarios and reports on those. There are no requirements to add these tests to new components yet, but it is encouraged. The tests are located in a separate app called “backoffice-e2e”. - ## Putting it all together When we are finished with the dashboard we will hopefully have something akin to this [real-world example of the actual dashboard that was migrated](https://github.com/umbraco/Umbraco.CMS.Backoffice/tree/main/src/backoffice/settings/dashboards/published-status). diff --git a/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/01_bug_report.md b/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/01_bug_report.md new file mode 100644 index 0000000000..e48baa3152 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/01_bug_report.md @@ -0,0 +1,32 @@ +--- +name: "\U0001F41B Bug report" +about: Create a report to help us improve +title: "[BUG]: " +labels: type/bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/feature_request.md b/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/02_feature_request.md similarity index 91% rename from src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/feature_request.md rename to src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/02_feature_request.md index bbcbbe7d61..d82f25d7c1 100644 --- a/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/feature_request.md +++ b/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/02_feature_request.md @@ -1,8 +1,8 @@ --- -name: Feature request +name: "✨ Feature request" about: Suggest an idea for this project title: '' -labels: '' +labels: type/feature assignees: '' --- diff --git a/src/Umbraco.Web.UI.Client/.gitignore b/src/Umbraco.Web.UI.Client/.gitignore index 8a626a2202..97d4759820 100644 --- a/src/Umbraco.Web.UI.Client/.gitignore +++ b/src/Umbraco.Web.UI.Client/.gitignore @@ -11,7 +11,7 @@ node_modules dist dist-cms dist-ssr -types +/types *.tsbuildinfo *.local *.tgz diff --git a/src/Umbraco.Web.UI.Client/.vscode/settings.json b/src/Umbraco.Web.UI.Client/.vscode/settings.json index 5f8ffac012..9ead1e3665 100644 --- a/src/Umbraco.Web.UI.Client/.vscode/settings.json +++ b/src/Umbraco.Web.UI.Client/.vscode/settings.json @@ -8,6 +8,7 @@ "Niels", "pickable", "templating", + "tinymce", "umbraco", "Uncategorized", "variantable" diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 537a7e1249..1eb592822c 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -41,23 +41,23 @@ "@types/lodash-es": "^4.17.6", "@types/mocha": "^10.0.0", "@types/uuid": "^9.0.0", - "@typescript-eslint/eslint-plugin": "^5.59.9", - "@typescript-eslint/parser": "^5.59.9", + "@typescript-eslint/eslint-plugin": "^5.60.1", + "@typescript-eslint/parser": "^5.60.1", "@web/dev-server-esbuild": "^0.3.3", "@web/dev-server-import-maps": "^0.1.1", "@web/dev-server-rollup": "^0.3.21", "@web/test-runner": "^0.16.1", "@web/test-runner-playwright": "^0.10.0", "babel-loader": "^9.1.2", - "eslint": "^8.32.0", + "eslint": "^8.43.0", "eslint-config-prettier": "^8.8.0", - "eslint-import-resolver-typescript": "^3.5.3", - "eslint-plugin-import": "^2.27.4", - "eslint-plugin-lit": "^1.8.2", - "eslint-plugin-lit-a11y": "^2.3.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-lit": "^1.8.3", + "eslint-plugin-lit-a11y": "^3.0.0", "eslint-plugin-local-rules": "^1.3.2", "eslint-plugin-storybook": "^0.6.12", - "eslint-plugin-wc": "^1.4.0", + "eslint-plugin-wc": "^1.5.0", "msw": "^1.2.1", "openapi-typescript-codegen": "^0.24.0", "playwright-msw": "^2.1.1", @@ -3063,9 +3063,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", - "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", + "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3087,9 +3087,9 @@ "dev": true }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -6394,15 +6394,15 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz", - "integrity": "sha512-4uQIBq1ffXd2YvF7MAvehWKW3zVv/w+mSfRAu+8cKbfj3nwzyqJLNcZJpQ/WZ1HLbJDiowwmQ6NO+63nCA+fqA==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz", + "integrity": "sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.9", - "@typescript-eslint/type-utils": "5.59.9", - "@typescript-eslint/utils": "5.59.9", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/type-utils": "5.60.1", + "@typescript-eslint/utils": "5.60.1", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -6461,14 +6461,14 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.9.tgz", - "integrity": "sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz", + "integrity": "sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.9", - "@typescript-eslint/types": "5.59.9", - "@typescript-eslint/typescript-estree": "5.59.9", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", "debug": "^4.3.4" }, "engines": { @@ -6488,13 +6488,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz", - "integrity": "sha512-8RA+E+w78z1+2dzvK/tGZ2cpGigBZ58VMEHDZtpE1v+LLjzrYGc8mMaTONSxKyEkz3IuXFM0IqYiGHlCsmlZxQ==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", + "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.9", - "@typescript-eslint/visitor-keys": "5.59.9" + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6505,13 +6505,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.9.tgz", - "integrity": "sha512-ksEsT0/mEHg9e3qZu98AlSrONAQtrSTljL3ow9CGej8eRo7pe+yaC/mvTjptp23Xo/xIf2mLZKC6KPv4Sji26Q==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz", + "integrity": "sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.9", - "@typescript-eslint/utils": "5.59.9", + "@typescript-eslint/typescript-estree": "5.60.1", + "@typescript-eslint/utils": "5.60.1", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -6532,9 +6532,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.9.tgz", - "integrity": "sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", + "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6545,13 +6545,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.9.tgz", - "integrity": "sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", + "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.9", - "@typescript-eslint/visitor-keys": "5.59.9", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -6584,9 +6584,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -6605,17 +6605,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.9.tgz", - "integrity": "sha512-1PuMYsju/38I5Ggblaeb98TOoUvjhRvLpLa1DoTOFaLWqaXl/1iQ1eGurTXgBY58NUdtfTXKP5xBq7q9NDaLKg==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz", + "integrity": "sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.9", - "@typescript-eslint/types": "5.59.9", - "@typescript-eslint/typescript-estree": "5.59.9", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -6664,12 +6664,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.9.tgz", - "integrity": "sha512-bT7s0td97KMaLwpEBckbzj/YohnvXtqbe2XgqNvTl6RJVakY5mvENOTPvw5u66nljfZxthESpDozs86U+oLY8Q==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", + "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/types": "5.60.1", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -11050,16 +11050,16 @@ } }, "node_modules/eslint": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", - "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", + "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.41.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint/js": "8.43.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -11288,9 +11288,9 @@ } }, "node_modules/eslint-plugin-lit-a11y": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-lit-a11y/-/eslint-plugin-lit-a11y-2.4.1.tgz", - "integrity": "sha512-UljRja/2cVrNtgnCDj5sCT3Larxda4mGqbsPhlksvECo0+KCD8EuUori/P6wFeFqk+pHlkIC3W200E5q85E3VQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit-a11y/-/eslint-plugin-lit-a11y-3.0.0.tgz", + "integrity": "sha512-+HMLB0XVDh6hX9rJZqmfkSfOuTSXzjab/gmDSbhIr/i60aUV+xLm1WdLAPAqLE5YsyxWd1CcycON4OO37ro0xA==", "dev": true, "dependencies": { "aria-query": "^5.1.3", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index c880d09784..ef261a4055 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -6,6 +6,7 @@ "exports": { ".": null, "./backend-api": "./dist-cms/external/backend-api/index.js", + "./class-api": "./dist-cms/libs/class-api/index.js", "./context-api": "./dist-cms/libs/context-api/index.js", "./controller-api": "./dist-cms/libs/controller-api/index.js", "./element-api": "./dist-cms/libs/element-api/index.js", @@ -34,13 +35,14 @@ "./modal": "./dist-cms/packages/core/modal/index.js", "./notification": "./dist-cms/packages/core/notification/index.js", "./picker-input": "./dist-cms/packages/core/picker-input/index.js", + "./property-editor": "./dist-cms/packages/core/property-editor/index.js", "./section": "./dist-cms/packages/core/section/index.js", "./sorter": "./dist-cms/packages/core/sorter/index.js", "./store": "./dist-cms/packages/core/store/index.js", + "./themes": "./dist-cms/packages/core/themes/index.ts", "./tree": "./dist-cms/packages/core/tree/index.js", "./variant": "./dist-cms/packages/core/variant/index.js", "./workspace": "./dist-cms/packages/core/workspace/index.js", - "./property-editor": "./dist-cms/packages/core/property-editor/index.js", "./document": "./dist-cms/packages/documents/documents/index.ts", "./document-blueprint": "./dist-cms/packages/documents/document-blueprints/index.ts", "./document-type": "./dist-cms/packages/documents/document-types/index.ts", @@ -54,7 +56,6 @@ "./language": "./dist-cms/packages/settings/languages/index.ts", "./logviewer": "./dist-cms/packages/settings/logviewer/index.js", "./relation-type": "./dist-cms/packages/settings/relation-types/index.ts", - "./themes": "./dist-cms/packages/settings/themes/index.ts", "./tags": "./dist-cms/packages/tags/index.ts", "./partial-view": "./dist-cms/packages/templating/partial-views/index.ts", "./stylesheet": "./dist-cms/packages/templating/stylesheets/index.ts", @@ -152,23 +153,23 @@ "@types/lodash-es": "^4.17.6", "@types/mocha": "^10.0.0", "@types/uuid": "^9.0.0", - "@typescript-eslint/eslint-plugin": "^5.59.9", - "@typescript-eslint/parser": "^5.59.9", + "@typescript-eslint/eslint-plugin": "^5.60.1", + "@typescript-eslint/parser": "^5.60.1", "@web/dev-server-esbuild": "^0.3.3", "@web/dev-server-import-maps": "^0.1.1", "@web/dev-server-rollup": "^0.3.21", "@web/test-runner": "^0.16.1", "@web/test-runner-playwright": "^0.10.0", "babel-loader": "^9.1.2", - "eslint": "^8.32.0", + "eslint": "^8.43.0", "eslint-config-prettier": "^8.8.0", - "eslint-import-resolver-typescript": "^3.5.3", - "eslint-plugin-import": "^2.27.4", - "eslint-plugin-lit": "^1.8.2", - "eslint-plugin-lit-a11y": "^2.3.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-lit": "^1.8.3", + "eslint-plugin-lit-a11y": "^3.0.0", "eslint-plugin-local-rules": "^1.3.2", "eslint-plugin-storybook": "^0.6.12", - "eslint-plugin-wc": "^1.4.0", + "eslint-plugin-wc": "^1.5.0", "msw": "^1.2.1", "openapi-typescript-codegen": "^0.24.0", "playwright-msw": "^2.1.1", diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/extension.controller.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/extension.controller.ts index 95611d9782..7ced82f8a8 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/extension.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/extension.controller.ts @@ -1,20 +1,18 @@ import { Subject } from '@umbraco-cms/backoffice/external/rxjs'; import { PackageResource, OpenAPI } from '@umbraco-cms/backoffice/backend-api'; -import { UmbController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBaseController, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; import { ManifestBase, isManifestJSType } from '@umbraco-cms/backoffice/extension-api'; -export class UmbExtensionInitializer extends UmbController { - host: UmbControllerHostElement; +export class UmbExtensionInitializer extends UmbBaseController { #extensionRegistry: UmbBackofficeExtensionRegistry; #unobserve = new Subject(); #localPackages: Array> = []; #apiBaseUrl = OpenAPI.BASE; - constructor(host: UmbControllerHostElement, extensionRegistry: UmbBackofficeExtensionRegistry) { + constructor(host: UmbControllerHost, extensionRegistry: UmbBackofficeExtensionRegistry) { super(host, UmbExtensionInitializer.name); - this.host = host; this.#extensionRegistry = extensionRegistry; } @@ -41,13 +39,13 @@ export class UmbExtensionInitializer extends UmbController { async #loadServerPackages() { /* TODO: we need a new endpoint here, to remove the dependency on the package repository, to get the modules available for the backoffice scope - / we will need a similar endpoint for the login, installer etc at some point. + / we will need a similar endpoint for the login, installer etc at some point. We should expose more information about the packages when not authorized so the end point should only return a list of modules from the manifest with with the correct scope. This code is copy pasted from the package repository. We probably don't need this is the package repository anymore. */ - const { data: packages } = await tryExecuteAndNotify(this.host, PackageResource.getPackageManifest()); + const { data: packages } = await tryExecuteAndNotify(this._host, PackageResource.getPackageManifest()); if (packages) { // Append packages to the store but only if they have a name diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts new file mode 100644 index 0000000000..b7f7cc9dcd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts @@ -0,0 +1,23 @@ +import type { UmbControllerHost } from '../controller-api/controller-host.interface.js'; +import type { UmbObserverController } from '../observable-api/index.js'; +import type { + UmbContextCallback, + UmbContextConsumerController, + UmbContextProviderController, + UmbContextToken, +} from '../context-api/index.js'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import { UmbController } from '@umbraco-cms/backoffice/controller-api'; + +export interface UmbClassMixinInterface extends UmbControllerHost, UmbController { + observe( + source: Observable | { asObservable: () => Observable }, + callback: (_value: T) => void, + unique?: string + ): UmbObserverController; + provideContext(alias: string | UmbContextToken, instance: R): UmbContextProviderController; + consumeContext( + alias: string | UmbContextToken, + callback: UmbContextCallback + ): UmbContextConsumerController; +} diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts new file mode 100644 index 0000000000..a58f3c0a9b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts @@ -0,0 +1,117 @@ +import type { UmbClassMixinInterface } from './class.interface.js'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import type { ClassConstructor } from '@umbraco-cms/backoffice/extension-api'; +import { + type UmbControllerHost, + UmbControllerHostBaseMixin, + UmbController, + UmbControllerAlias, +} from '@umbraco-cms/backoffice/controller-api'; +import { + UmbContextToken, + UmbContextCallback, + UmbContextConsumerController, + UmbContextProviderController, +} from '@umbraco-cms/backoffice/context-api'; +import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; + +type UmbClassMixinConstructor = new ( + host: UmbControllerHost, + controllerAlias: UmbControllerAlias +) => UmbClassMixinDeclaration; + +declare class UmbClassMixinDeclaration implements UmbClassMixinInterface { + _host: UmbControllerHost; + observe( + source: Observable | { asObservable: () => Observable }, + callback: (_value: T) => void, + controllerAlias?: UmbControllerAlias + ): UmbObserverController; + provideContext(alias: string | UmbContextToken, instance: R): UmbContextProviderController; + consumeContext( + alias: string | UmbContextToken, + callback: UmbContextCallback + ): UmbContextConsumerController; + hasController(controller: UmbController): boolean; + getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[]; + addController(controller: UmbController): void; + removeControllerByAlias(controllerAlias: UmbControllerAlias): void; + removeController(controller: UmbController): void; + getHostElement(): EventTarget; + + get controllerAlias(): UmbControllerAlias; + hostConnected(): void; + hostDisconnected(): void; + destroy(): void; +} + +export const UmbClassMixin = (superClass: T) => { + class UmbClassMixinClass extends UmbControllerHostBaseMixin(superClass) implements UmbControllerHost { + protected _host: UmbControllerHost; + protected _controllerAlias: UmbControllerAlias; + + constructor(host: UmbControllerHost, controllerAlias: UmbControllerAlias) { + super(); + this._host = host; + this._controllerAlias = controllerAlias ?? undefined; // ?? Symbol(); + } + + getHostElement(): EventTarget { + return this._host.getHostElement(); + } + + get controllerAlias(): UmbControllerAlias { + return this._controllerAlias; + } + + /** + * @description Observe a RxJS source of choice. + * @param {Observable} source RxJS source + * @param {method} callback Callback method called when data is changed. + * @return {UmbObserverController} Reference to a Observer Controller instance + * @memberof UmbElementMixin + */ + observe( + source: Observable | { asObservable: () => Observable }, + callback: (_value: T) => void, + controllerAlias?: UmbControllerAlias + ) { + return new UmbObserverController( + this, + (source as any).asObservable ? (source as any).asObservable() : source, + callback, + controllerAlias + ); + } + + /** + * @description Provide a context API for this or child elements. + * @param {string} contextAlias + * @param {instance} instance The API instance to be exposed. + * @return {UmbContextProviderController} Reference to a Context Provider Controller instance + * @memberof UmbElementMixin + */ + provideContext( + contextAlias: string | UmbContextToken, + instance: R + ): UmbContextProviderController { + return new UmbContextProviderController(this, contextAlias, instance); + } + + /** + * @description Setup a subscription for a context. The callback is called when the context is resolved. + * @param {string} contextAlias + * @param {method} callback Callback method called when context is resolved. + * @return {UmbContextConsumerController} Reference to a Context Consumer Controller instance + * @memberof UmbElementMixin + */ + consumeContext( + contextAlias: string | UmbContextToken, + callback: UmbContextCallback + ): UmbContextConsumerController { + return new UmbContextConsumerController(this, contextAlias, callback); + } + } + + return UmbClassMixinClass as unknown as UmbClassMixinConstructor & UmbClassMixinDeclaration; +}; diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/index.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/index.ts new file mode 100644 index 0000000000..a6874daafa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/index.ts @@ -0,0 +1,2 @@ +export * from './class.interface.js'; +export * from './class.mixin.js'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts index 0eb139f840..a09e9a6d72 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts @@ -1,29 +1,26 @@ import { UmbContextToken } from '../token/context-token.js'; import { UmbContextConsumer } from './context-consumer.js'; import { UmbContextCallback } from './context-request.event.js'; -import type { UmbControllerHostElement, UmbControllerInterface } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api'; -export class UmbContextConsumerController - extends UmbContextConsumer - implements UmbControllerInterface -{ - public get unique() { - return undefined; +export class UmbContextConsumerController extends UmbContextConsumer implements UmbController { + #controllerAlias = Symbol(); + #host: UmbControllerHost; + + public get controllerAlias() { + return this.#controllerAlias; } - constructor( - host: UmbControllerHostElement, - contextAlias: string | UmbContextToken, - callback: UmbContextCallback - ) { - super(host, contextAlias, callback); + constructor(host: UmbControllerHost, contextAlias: string | UmbContextToken, callback: UmbContextCallback) { + super(host.getHostElement(), contextAlias, callback); + this.#host = host; host.addController(this); } public destroy() { super.destroy(); - if (this.host) { - this.host.removeController(this); + if (this.#host) { + this.#host.removeController(this); } } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts index 528da94673..e02408b5d0 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts @@ -6,50 +6,48 @@ import { UmbContextRequestEventImplementation, UmbContextCallback } from './cont * @export * @class UmbContextConsumer */ -export class UmbContextConsumer { - _promise?: Promise; - _promiseResolver?: (instance: T) => void; +export class UmbContextConsumer { + #callback?: UmbContextCallback; + #promise?: Promise; + #promiseResolver?: (instance: T) => void; - private _instance?: T; + #instance?: T; get instance() { - return this._instance; + return this.#instance; } - private _contextAlias: string; - get consumerAlias(): string { - return this._contextAlias; - } + #contextAlias: string; /** * Creates an instance of UmbContextConsumer. - * @param {EventTarget} host - * @param {string} _contextAlias + * @param {EventTarget} hostElement + * @param {string} contextAlias * @param {UmbContextCallback} _callback * @memberof UmbContextConsumer */ constructor( - protected host: HostType, - _contextAlias: string | UmbContextToken, - private _callback?: UmbContextCallback + protected hostElement: EventTarget, + contextAlias: string | UmbContextToken, + callback?: UmbContextCallback ) { - this._contextAlias = _contextAlias.toString(); + this.#contextAlias = contextAlias.toString(); + this.#callback = callback; } protected _onResponse = (instance: T) => { - // TODO: check that this check is not giving us any problems: - if (this._instance === instance) { + if (this.#instance === instance) { return; } - this._instance = instance; - this._callback?.(instance); - this._promiseResolver?.(instance); + this.#instance = instance; + this.#callback?.(instance); + this.#promiseResolver?.(instance); }; public asPromise() { return ( - this._promise || - (this._promise = new Promise((resolve) => { - this._instance ? resolve(this._instance) : (this._promiseResolver = resolve); + this.#promise || + (this.#promise = new Promise((resolve) => { + this.#instance ? resolve(this.#instance) : (this.#promiseResolver = resolve); })) ); } @@ -58,8 +56,8 @@ export class UmbContextConsumer { if (!isUmbContextProvideEventType(event)) return; - if (this._contextAlias === event.contextAlias) { + if (this.#contextAlias === event.contextAlias) { this.request(); } }; // TODO: Test destroy scenarios: public destroy() { - delete this._instance; - delete this._callback; - delete this._promise; - delete this._promiseResolver; + this.hostDisconnected(); + this.#callback = undefined; + this.#promise = undefined; + this.#promiseResolver = undefined; + this.#instance = undefined; } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/is-context-consumer-type.function.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/is-context-consumer-type.function.ts deleted file mode 100644 index 74f4af6127..0000000000 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/is-context-consumer-type.function.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { UmbContextConsumer } from './context-consumer.js'; - -export function isContextConsumerType(instance: unknown): instance is UmbContextConsumer { - return ( - typeof instance === 'object' && instance !== null && (instance as UmbContextConsumer).consumerAlias !== undefined - ); -} diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.test.ts index 1accbd7eb7..559b119efb 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.test.ts @@ -23,11 +23,11 @@ describe('UmbContextProviderController', () => { describe('Public API', () => { describe('properties', () => { - it('has a unique property', () => { - expect(provider).to.have.property('unique'); + it('has a controllerAlias property', () => { + expect(provider).to.have.property('controllerAlias'); }); - it('has a unique property, is equal to the unique', () => { - expect(provider.unique).to.eq('my-test-context'); + it('has a controllerAlias property, is equal to the controllerAlias', () => { + expect(provider.controllerAlias).to.eq('my-test-context'); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts index 6d7aef9cd5..f049ef675e 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts @@ -1,27 +1,27 @@ import { UmbContextToken } from '../token/context-token.js'; import { UmbContextProvider } from './context-provider.js'; -import type { UmbControllerHostElement, UmbControllerInterface } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api'; -export class UmbContextProviderController - extends UmbContextProvider - implements UmbControllerInterface -{ - public get unique() { +export class UmbContextProviderController extends UmbContextProvider implements UmbController { + #host: UmbControllerHost; + + public get controllerAlias() { return this._contextAlias.toString(); } - constructor(host: UmbControllerHostElement, contextAlias: string | UmbContextToken, instance: T) { - super(host, contextAlias, instance); + constructor(host: UmbControllerHost, contextAlias: string | UmbContextToken, instance: T) { + super(host.getHostElement(), contextAlias, instance); + this.#host = host; // If this API is already provided with this alias? Then we do not want to register this controller: - const existingControllers = host.getControllers((x) => x.unique === this.unique); + const existingControllers = host.getControllers((x) => x.controllerAlias === this.controllerAlias); if ( existingControllers.length > 0 && (existingControllers[0] as UmbContextProviderController).providerInstance?.() === instance ) { // Back out, this instance is already provided, by another controller. throw new Error( - `Context API: The context of '${this.unique}' is already provided with the same API by another Context Provider Controller.` + `Context API: The context of '${this.controllerAlias}' is already provided with the same API by another Context Provider Controller.` ); } else { host.addController(this); @@ -30,8 +30,8 @@ export class UmbContextProviderController public destroy() { super.destroy(); - if (this.host) { - this.host.removeController(this); + if (this.#host) { + this.#host.removeController(this); } } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.element.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.element.test.ts index 5dbf8821eb..9696eb2f56 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.element.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.element.test.ts @@ -2,10 +2,10 @@ import { expect, fixture, html } from '@open-wc/testing'; import { UmbContextConsumerController } from '../consume/context-consumer.controller.js'; import { UmbContextProviderElement } from './context-provider.element.js'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; -import { UmbControllerHostMixin } from '@umbraco-cms/backoffice/controller-api'; +import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; @customElement('umb-test-context') -export class UmbTestContextElement extends UmbControllerHostMixin(HTMLElement) { +export class UmbTestContextElement extends UmbControllerHostElementMixin(HTMLElement) { public value: string | null = null; constructor() { super(); diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.element.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.element.ts index d43da09de2..445e69a933 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.element.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.element.ts @@ -1,4 +1,4 @@ -import { UmbControllerHostElement, UmbControllerHostMixin } from '@umbraco-cms/backoffice/controller-api'; +import { UmbControllerHostElement, UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextProviderController, UmbContextToken } from '@umbraco-cms/backoffice/context-api'; /** @@ -9,7 +9,7 @@ import { UmbContextProviderController, UmbContextToken } from '@umbraco-cms/back * @throws {Error} If the key property is not set. * @throws {Error} If the value property is not set. */ -export class UmbContextProviderElement extends UmbControllerHostMixin(HTMLElement) { +export class UmbContextProviderElement extends UmbControllerHostElementMixin(HTMLElement) { /** * The value to provide to the context. * @optional diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts index 7b6638a657..4ca44b90c0 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts @@ -23,8 +23,8 @@ describe('UmbContextProvider', () => { describe('Public API', () => { describe('properties', () => { - it('has a host property', () => { - expect(provider).to.have.property('host'); + it('has a hostElement property', () => { + expect(provider).to.have.property('hostElement'); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts index add39e4e26..8dc303c0e6 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts @@ -10,8 +10,8 @@ import { UmbContextProvideEventImplementation } from './context-provide.event.js * @export * @class UmbContextProvider */ -export class UmbContextProvider { - protected host: HostType; +export class UmbContextProvider { + protected hostElement: EventTarget; protected _contextAlias: string; #instance: unknown; @@ -32,8 +32,8 @@ export class UmbContextProvider { * @param {*} instance * @memberof UmbContextProvider */ - constructor(host: HostType, contextAlias: string | UmbContextToken, instance: unknown) { - this.host = host; + constructor(hostElement: EventTarget, contextAlias: string | UmbContextToken, instance: unknown) { + this.hostElement = hostElement; this._contextAlias = contextAlias.toString(); this.#instance = instance; } @@ -42,18 +42,18 @@ export class UmbContextProvider { * @memberof UmbContextProvider */ public hostConnected() { - this.host.addEventListener(umbContextRequestEventType, this._handleContextRequest); - this.host.dispatchEvent(new UmbContextProvideEventImplementation(this._contextAlias)); + this.hostElement.addEventListener(umbContextRequestEventType, this._handleContextRequest); + this.hostElement.dispatchEvent(new UmbContextProvideEventImplementation(this._contextAlias)); // Listen to our debug event 'umb:debug-contexts' - this.host.addEventListener(umbDebugContextEventType, this._handleDebugContextRequest); + this.hostElement.addEventListener(umbDebugContextEventType, this._handleDebugContextRequest); } /** * @memberof UmbContextProvider */ public hostDisconnected() { - this.host.removeEventListener(umbContextRequestEventType, this._handleContextRequest); + this.hostElement.removeEventListener(umbContextRequestEventType, this._handleContextRequest); // TODO: fire unprovided event. } diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts new file mode 100644 index 0000000000..e25e734e02 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts @@ -0,0 +1 @@ +export type UmbControllerAlias = string | symbol | undefined; diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-base.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-base.mixin.ts new file mode 100644 index 0000000000..5d284e936c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-base.mixin.ts @@ -0,0 +1,114 @@ +import { ClassConstructor } from '../extension-api/types.js'; +import { UmbControllerHost } from './controller-host.interface.js'; +import type { UmbController } from './controller.interface.js'; + +declare class UmbControllerHostBaseDeclaration implements Omit { + hasController(controller: UmbController): boolean; + getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[]; + addController(controller: UmbController): void; + removeControllerByAlias(unique: UmbController['controllerAlias']): void; + removeController(controller: UmbController): void; + + hostConnected(): void; + hostDisconnected(): void; + destroy(): void; +} + +/** + * This mixin enables a class to host controllers. + * This enables controllers to be added to the life cycle of this element. + * + * @param {Object} superClass - superclass to be extended. + * @mixin + */ +export const UmbControllerHostBaseMixin = >(superClass: T) => { + class UmbControllerHostBaseClass extends superClass { + #controllers: UmbController[] = []; + + #attached = false; + + /** + * Tests if a controller is assigned to this element. + * @param {UmbController} ctrl + */ + hasController(ctrl: UmbController): boolean { + return this.#controllers.indexOf(ctrl) !== -1; + } + + /** + * Retrieve controllers matching a filter of this element. + * @param {method} filterMethod + */ + getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[] { + return this.#controllers.filter(filterMethod); + } + + /** + * Append a controller to this element. + * @param {UmbController} ctrl + */ + addController(ctrl: UmbController): void { + // If this specific class is already added, then skip out. + if (this.#controllers.indexOf(ctrl) !== -1) { + return; + } + + // Check if there is one already with same unique + this.removeControllerByAlias(ctrl.controllerAlias); + + this.#controllers.push(ctrl); + if (this.#attached) { + // If a controller is created on a already attached element, then it will be added directly. This might not be optimal. As the controller it self has not finished its constructor method jet. therefor i postpone the call: + Promise.resolve().then(() => ctrl.hostConnected()); + //ctrl.hostConnected(); + } + } + + /** + * Remove a controller from this element. + * Notice this will also destroy the controller. + * @param {UmbController} ctrl + */ + removeController(ctrl: UmbController): void { + const index = this.#controllers.indexOf(ctrl); + if (index !== -1) { + this.#controllers.splice(index, 1); + if (this.#attached) { + ctrl.hostDisconnected(); + } + ctrl.destroy(); + } + } + + /** + * Remove a controller from this element by its alias. + * Notice this will also destroy the controller. + * @param {string | symbol} controllerAlias + */ + removeControllerByAlias(controllerAlias: UmbController['controllerAlias']): void { + if (controllerAlias) { + this.#controllers.forEach((x) => { + if (x.controllerAlias === controllerAlias) { + this.removeController(x); + } + }); + } + } + + hostConnected() { + this.#attached = true; + this.#controllers.forEach((ctrl: UmbController) => ctrl.hostConnected()); + } + + hostDisconnected() { + this.#attached = false; + this.#controllers.forEach((ctrl: UmbController) => ctrl.hostDisconnected()); + } + + destroy() { + this.#controllers.forEach((ctrl: UmbController) => ctrl.destroy()); + } + } + + return UmbControllerHostBaseClass as unknown as ClassConstructor & T; +}; diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-element.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-element.mixin.ts new file mode 100644 index 0000000000..28605231ce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-element.mixin.ts @@ -0,0 +1,48 @@ +import { HTMLElementConstructor } from '../extension-api/types.js'; +import { UmbControllerAlias } from './controller-alias.type.js'; +import { UmbControllerHostBaseMixin } from './controller-host-base.mixin.js'; +import { UmbControllerHost } from './controller-host.interface.js'; +import type { UmbController } from './controller.interface.js'; + +export declare class UmbControllerHostElement extends HTMLElement implements UmbControllerHost { + hasController(controller: UmbController): boolean; + getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[]; + addController(controller: UmbController): void; + removeControllerByAlias(alias: UmbControllerAlias): void; + removeController(controller: UmbController): void; + getHostElement(): EventTarget; +} + +/** + * This mixin enables a web-component to host controllers. + * This enables controllers to be added to the life cycle of this element. + * + * @param {Object} superClass - superclass to be extended. + * @mixin + */ +export const UmbControllerHostElementMixin = (superClass: T) => { + class UmbControllerHostElementClass extends UmbControllerHostBaseMixin(superClass) implements UmbControllerHost { + getHostElement(): EventTarget { + return this; + } + + connectedCallback() { + super.connectedCallback?.(); + this.hostConnected(); + } + + disconnectedCallback() { + super.disconnectedCallback?.(); + this.hostDisconnected(); + } + } + + return UmbControllerHostElementClass as unknown as HTMLElementConstructor & T; +}; + +declare global { + interface HTMLElement { + connectedCallback(): void; + disconnectedCallback(): void; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-initializer.element.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-initializer.element.ts index 567adf2ec4..bb31cbe4f9 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-initializer.element.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-initializer.element.ts @@ -1,7 +1,7 @@ -import { UmbControllerHostElement, UmbControllerHostMixin } from './controller-host.mixin.js'; +import { UmbControllerHostElement, UmbControllerHostElementMixin } from './controller-host-element.mixin.js'; export class UmbControllerHostInitializerElement - extends UmbControllerHostMixin(HTMLElement) + extends UmbControllerHostElementMixin(HTMLElement) implements UmbControllerHostElement { /** diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-initializer.test.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-initializer.test.ts index 635e6f5147..afb7ad833d 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-initializer.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host-initializer.test.ts @@ -1,11 +1,11 @@ import { expect, fixture, html } from '@open-wc/testing'; import { UmbControllerHostInitializerElement } from './controller-host-initializer.element.js'; -import { UmbControllerHostElement, UmbControllerHostMixin } from './controller-host.mixin.js'; +import { UmbControllerHostElement, UmbControllerHostElementMixin } from './controller-host-element.mixin.js'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbContextConsumerController, UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; @customElement('umb-test-controller-host-initializer-consumer') -export class UmbTestControllerHostInitializerConsumerElement extends UmbControllerHostMixin(HTMLElement) { +export class UmbTestControllerHostInitializerConsumerElement extends UmbControllerHostElementMixin(HTMLElement) { public value: string | null = null; constructor() { super(); diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.interface.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.interface.ts new file mode 100644 index 0000000000..243a2e3791 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.interface.ts @@ -0,0 +1,10 @@ +import type { UmbController } from './controller.interface.js'; + +export declare class UmbControllerHost { + hasController(controller: UmbController): boolean; + getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[]; + addController(controller: UmbController): void; + removeControllerByAlias(unique: UmbController['controllerAlias']): void; + removeController(controller: UmbController): void; + getHostElement(): EventTarget; +} diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts deleted file mode 100644 index aff22afaff..0000000000 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts +++ /dev/null @@ -1,122 +0,0 @@ -import type { UmbControllerInterface } from './controller.interface.js'; - -type HTMLElementConstructor = new (...args: any[]) => T; - -export declare class UmbControllerHostElement extends HTMLElement { - hasController(controller: UmbControllerInterface): boolean; - getControllers(filterMethod: (ctrl: UmbControllerInterface) => boolean): UmbControllerInterface[]; - addController(controller: UmbControllerInterface): void; - removeControllerByUnique(unique: UmbControllerInterface['unique']): void; - removeController(controller: UmbControllerInterface): void; -} - -/** - * This mixin enables the component to host controllers. - * This is done by calling the `consumeContext` method. - * - * @param {Object} superClass - superclass to be extended. - * @mixin - */ -export const UmbControllerHostMixin = (superClass: T) => { - class UmbContextConsumerClass extends superClass { - #controllers: UmbControllerInterface[] = []; - - #attached = false; - - /** - * Tests if a controller is assigned to this element. - * @param {UmbControllerInterface} ctrl - */ - hasController(ctrl: UmbControllerInterface): boolean { - return this.#controllers.indexOf(ctrl) !== -1; - } - - /** - * Retrieve controllers matching a filter of this element. - * @param {method} filterMethod - */ - getControllers(filterMethod: (ctrl: UmbControllerInterface) => boolean): UmbControllerInterface[] { - return this.#controllers.filter(filterMethod); - } - - /** - * Append a controller to this element. - * @param {UmbControllerInterface} ctrl - */ - addController(ctrl: UmbControllerInterface): void { - // Check if there is one already with same unique - this.removeControllerByUnique(ctrl.unique); - - this.#controllers.push(ctrl); - if (this.#attached) { - // If a controller is created on a already attached element, then it will be added directly. This might not be optimal. As the controller it self has not finished its constructor method jet. therefor i postpone the call: - Promise.resolve().then(() => ctrl.hostConnected()); - //ctrl.hostConnected(); - } - } - - /** - * Remove a controller from this element, by its unique/alias. - * @param {unknown} unique/alias - */ - removeControllerByUnique(unique: UmbControllerInterface['unique']): void { - if (unique) { - this.#controllers.forEach((x) => { - if (x.unique === unique) { - this.removeController(x); - } - }); - } - } - - /** - * Remove a controller from this element. - * Notice this will also destroy the controller. - * @param {UmbControllerInterface} ctrl - */ - removeController(ctrl: UmbControllerInterface): void { - const index = this.#controllers.indexOf(ctrl); - if (index !== -1) { - this.#controllers.splice(index, 1); - if (this.#attached) { - ctrl.hostDisconnected(); - } - ctrl.destroy(); - } - } - - /** - * Remove a controller from this element by its alias. - * Notice this will also destroy the controller. - * @param {string} unique - */ - removeControllerByAlias(unique: string): void { - this.#controllers.forEach((x) => { - if (x.unique === unique) { - this.removeController(x); - } - }); - } - - connectedCallback() { - super.connectedCallback?.(); - this.#attached = true; - this.#controllers.forEach((ctrl: UmbControllerInterface) => ctrl.hostConnected()); - } - - disconnectedCallback() { - super.disconnectedCallback?.(); - this.#attached = false; - this.#controllers.forEach((ctrl: UmbControllerInterface) => ctrl.hostDisconnected()); - } - } - - return UmbContextConsumerClass as unknown as HTMLElementConstructor & T; -}; - -declare global { - interface HTMLElement { - connectedCallback(): void; - disconnectedCallback(): void; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.class.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.class.ts index ba356055b2..c052ddb873 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.class.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.class.ts @@ -1,27 +1,23 @@ -import { UmbControllerHostElement } from './controller-host.mixin.js'; -import type { UmbControllerInterface } from './controller.interface.js'; +import { UmbClassMixin } from '../class-api/index.js'; +import { UmbControllerHost } from './controller-host.interface.js'; +import { UmbController } from './controller.interface.js'; -export abstract class UmbController implements UmbControllerInterface { - protected host?: UmbControllerHostElement; - - private _alias?: string; - public get unique() { - return this._alias; +/** + * This mixin enables a web-component to host controllers. + * This enables controllers to be added to the life cycle of this element. + * + */ +export abstract class UmbBaseController extends UmbClassMixin(class {}) implements UmbController { + constructor(host: UmbControllerHost, controllerAlias?: UmbController['controllerAlias']) { + super(host, controllerAlias); + this._host.addController(this); } - constructor(host: UmbControllerHostElement, alias?: string) { - this.host = host; - this._alias = alias; - this.host.addController(this); - } - - abstract hostConnected(): void; - abstract hostDisconnected(): void; - public destroy() { - if (this.host) { - this.host.removeController(this); + if (this._host) { + this._host.removeController(this); } - delete this.host; + //delete this.host; + super.destroy(); } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.interface.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.interface.ts index f8feb3ff9c..a5fa4dc1e3 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.interface.ts @@ -1,5 +1,6 @@ -export interface UmbControllerInterface { - get unique(): string | undefined; +import type { UmbControllerAlias } from './controller-alias.type.js'; +export interface UmbController { + get controllerAlias(): UmbControllerAlias; hostConnected(): void; hostDisconnected(): void; destroy(): void; diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts index b3dd76d18a..65f4f8e2e1 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts @@ -1,44 +1,198 @@ import { expect } from '@open-wc/testing'; -import { UmbControllerHostElement, UmbControllerHostMixin } from './controller-host.mixin.js'; +import { UmbControllerHostElement, UmbControllerHostElementMixin } from './controller-host-element.mixin.js'; +import { UmbBaseController } from './controller.class.js'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; -import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; - -class UmbTestContext { - prop = 'value from provider'; -} @customElement('test-my-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostMixin(HTMLElement) {} +export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} -describe('UmbContextProvider', () => { +export class UmbTestControllerImplementationElement extends UmbBaseController { + testIsConnected = false; + testIsDestroyed = false; + + hostConnected(): void { + super.hostConnected(); + this.testIsConnected = true; + } + hostDisconnected(): void { + super.hostDisconnected(); + this.testIsConnected = false; + } + + public destroy(): void { + super.destroy(); + this.testIsDestroyed = true; + } +} + +describe('UmbController', () => { type NewType = UmbControllerHostElement; let hostElement: NewType; - const contextInstance = new UmbTestContext(); beforeEach(() => { hostElement = document.createElement('test-my-controller-host') as UmbControllerHostElement; }); - describe('Destroyed controllers is gone from host', () => { - it('has a host property', () => { - const ctrl = new UmbContextProviderController(hostElement, 'my-test-context', contextInstance); + describe('Controller Host Public API', () => { + describe('methods', () => { + it('has an hasController method', () => { + expect(hostElement).to.have.property('hasController').that.is.a('function'); + }); + it('has an getControllers method', () => { + expect(hostElement).to.have.property('getControllers').that.is.a('function'); + }); + it('has an addController method', () => { + expect(hostElement).to.have.property('addController').that.is.a('function'); + }); + it('has an removeControllerByAlias method', () => { + expect(hostElement).to.have.property('removeControllerByAlias').that.is.a('function'); + }); + it('has an removeController method', () => { + expect(hostElement).to.have.property('removeController').that.is.a('function'); + }); + it('has an hostConnected method', () => { + expect(hostElement).to.have.property('hostConnected').that.is.a('function'); + }); + it('has an hostDisconnected method', () => { + expect(hostElement).to.have.property('hostDisconnected').that.is.a('function'); + }); + it('has an destroy method', () => { + expect(hostElement).to.have.property('destroy').that.is.a('function'); + }); + }); + }); + + describe('Controller Public API', () => { + let controller: UmbTestControllerImplementationElement; + beforeEach(() => { + controller = new UmbTestControllerImplementationElement(hostElement, 'my-test-context'); + }); + + describe('methods', () => { + it('has an controllerAlias property', () => { + expect(controller).to.have.property('controllerAlias').that.is.a('string'); + }); + it('has an hasController method', () => { + expect(controller).to.have.property('hasController').that.is.a('function'); + }); + it('has an getControllers method', () => { + expect(controller).to.have.property('getControllers').that.is.a('function'); + }); + it('has an addController method', () => { + expect(controller).to.have.property('addController').that.is.a('function'); + }); + it('has an removeControllerByAlias method', () => { + expect(controller).to.have.property('removeControllerByAlias').that.is.a('function'); + }); + it('has an removeController method', () => { + expect(controller).to.have.property('removeController').that.is.a('function'); + }); + it('has an hostConnected method', () => { + expect(controller).to.have.property('hostConnected').that.is.a('function'); + }); + it('has an hostDisconnected method', () => { + expect(controller).to.have.property('hostDisconnected').that.is.a('function'); + }); + it('has an destroy method', () => { + expect(controller).to.have.property('destroy').that.is.a('function'); + }); + }); + }); + + describe('Controllers lifecycle', () => { + it('controller is removed from host when destroyed', () => { + const ctrl = new UmbTestControllerImplementationElement(hostElement, 'my-test-context'); + const subCtrl = new UmbTestControllerImplementationElement(ctrl, 'my-test-context'); expect(hostElement.hasController(ctrl)).to.be.true; + expect(ctrl.hasController(subCtrl)).to.be.true; ctrl.destroy(); expect(hostElement.hasController(ctrl)).to.be.false; + expect(ctrl.testIsConnected).to.be.false; + expect(ctrl.testIsDestroyed).to.be.true; + expect(ctrl.hasController(subCtrl)).to.be.false; + expect(subCtrl.testIsConnected).to.be.false; + expect(subCtrl.testIsDestroyed).to.be.true; + }); + + it('controller is destroyed when removed from host', () => { + const ctrl = new UmbTestControllerImplementationElement(hostElement, 'my-test-context'); + const subCtrl = new UmbTestControllerImplementationElement(ctrl, 'my-test-context'); + + expect(ctrl.testIsDestroyed).to.be.false; + expect(subCtrl.testIsDestroyed).to.be.false; + expect(hostElement.hasController(ctrl)).to.be.true; + expect(ctrl.hasController(subCtrl)).to.be.true; + + hostElement.removeController(ctrl); + + expect(ctrl.testIsDestroyed).to.be.true; + expect(hostElement.hasController(ctrl)).to.be.false; + expect(subCtrl.testIsDestroyed).to.be.true; + expect(ctrl.hasController(subCtrl)).to.be.false; + }); + + it('hostConnected & hostDisconnected is triggered accordingly to the state of the controller host.', () => { + const ctrl = new UmbTestControllerImplementationElement(hostElement, 'my-test-context'); + const subCtrl = new UmbTestControllerImplementationElement(ctrl, 'my-test-context'); + + expect(hostElement.hasController(ctrl)).to.be.true; + expect(ctrl.hasController(subCtrl)).to.be.true; + expect(ctrl.testIsConnected).to.be.false; + expect(subCtrl.testIsConnected).to.be.false; + + document.body.appendChild(hostElement); + + expect(ctrl.testIsConnected).to.be.true; + expect(subCtrl.testIsConnected).to.be.true; + + document.body.removeChild(hostElement); + + expect(ctrl.testIsConnected).to.be.false; + expect(subCtrl.testIsConnected).to.be.false; }); }); - describe('Unique controllers replace each other', () => { - it('has a host property', () => { - const firstCtrl = new UmbContextProviderController(hostElement, 'my-test-context', contextInstance); - const secondCtrl = new UmbContextProviderController(hostElement, 'my-test-context', new UmbTestContext()); + describe('Controllers against other Controller', () => { + it('controller is replaced by another controller using the same string as controller-alias', () => { + const firstCtrl = new UmbTestControllerImplementationElement(hostElement, 'my-test-context'); + const secondCtrl = new UmbTestControllerImplementationElement(hostElement, 'my-test-context'); expect(hostElement.hasController(firstCtrl)).to.be.false; expect(hostElement.hasController(secondCtrl)).to.be.true; }); + + it('controller is replaced by another controller using the the same symbol as controller-alias', () => { + const mySymbol = Symbol(); + const firstCtrl = new UmbTestControllerImplementationElement(hostElement, mySymbol); + const secondCtrl = new UmbTestControllerImplementationElement(hostElement, mySymbol); + + expect(hostElement.hasController(firstCtrl)).to.be.false; + expect(hostElement.hasController(secondCtrl)).to.be.true; + }); + + it('controller is not replacing another controller when using the undefined as controller-alias', () => { + const firstCtrl = new UmbTestControllerImplementationElement(hostElement, undefined); + const secondCtrl = new UmbTestControllerImplementationElement(hostElement, undefined); + + expect(hostElement.hasController(firstCtrl)).to.be.true; + expect(hostElement.hasController(secondCtrl)).to.be.true; + }); + + it('sub controllers is not replacing another sub controllers when using the same controller-alias', () => { + const mySymbol = Symbol(); + + const firstCtrl = new UmbTestControllerImplementationElement(hostElement, undefined); + const secondCtrl = new UmbTestControllerImplementationElement(hostElement, undefined); + + const firstSubCtrl = new UmbTestControllerImplementationElement(firstCtrl, mySymbol); + const secondSubCtrl = new UmbTestControllerImplementationElement(secondCtrl, mySymbol); + + expect(firstCtrl.hasController(firstSubCtrl)).to.be.true; + expect(secondCtrl.hasController(secondSubCtrl)).to.be.true; + }); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/index.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/index.ts index 245ba6e2a5..510264d65b 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/index.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/index.ts @@ -1,3 +1,6 @@ -export * from './controller-host.mixin.js'; +export * from './controller-host-base.mixin.js'; +export * from './controller-host.interface.js'; +export * from './controller-host-element.mixin.js'; export * from './controller.class.js'; export * from './controller.interface.js'; +export * from './controller-alias.type.js'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts index 0582586a0d..79f5328fb9 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts @@ -1,6 +1,6 @@ import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { HTMLElementConstructor } from '@umbraco-cms/backoffice/extension-api'; -import { UmbControllerHostMixin } from '@umbraco-cms/backoffice/controller-api'; +import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken, @@ -10,12 +10,7 @@ import { } from '@umbraco-cms/backoffice/context-api'; import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; -// TODO: can we use this aliases to generate the key of this type -interface ResolvedContexts { - [key: string]: any; -} - -export declare class UmbElementMixinInterface extends UmbControllerHostElement { +export declare class UmbElement extends UmbControllerHostElement { observe( source: Observable | { asObservable: () => Observable }, callback: (_value: T) => void, @@ -26,11 +21,10 @@ export declare class UmbElementMixinInterface extends UmbControllerHostElement { alias: string | UmbContextToken, callback: UmbContextCallback ): UmbContextConsumerController; - consumeAllContexts(contextAliases: string[], callback: (_instances: ResolvedContexts) => void): void; } export const UmbElementMixin = (superClass: T) => { - class UmbElementMixinClass extends UmbControllerHostMixin(superClass) implements UmbElementMixinInterface { + class UmbElementMixinClass extends UmbControllerHostElementMixin(superClass) implements UmbElement { /** * @description Observe a RxJS source of choice. * @param {Observable} source RxJS source @@ -75,34 +69,7 @@ export const UmbElementMixin = (superClass: T) ): UmbContextConsumerController { return new UmbContextConsumerController(this, alias, callback); } - - /** - * @description Setup a subscription for multiple contexts. The callback is called when all contexts are resolved. - * @param {string} aliases - * @param {method} callback Callback method called when all contexts are resolved. - * @memberof UmbElementMixin - * @deprecated it should not be necessary to consume multiple contexts at once, use consumeContext instead with an UmbContextToken - */ - consumeAllContexts(_contextAliases: Array, callback: (_instances: ResolvedContexts) => void) { - let resolvedAmount = 0; - const controllers = _contextAliases.map( - (alias) => - new UmbContextConsumerController(this, alias, () => { - resolvedAmount++; - - if (resolvedAmount === _contextAliases.length) { - const result: ResolvedContexts = {}; - - controllers.forEach((contextCtrl: UmbContextConsumerController) => { - result[contextCtrl.consumerAlias?.toString()] = contextCtrl.instance; - }); - - callback(result); - } - }) - ); - } } - return UmbElementMixinClass as unknown as HTMLElementConstructor & T; + return UmbElementMixinClass as unknown as HTMLElementConstructor & T; }; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/bundle-extension-initializer.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/bundle-extension-initializer.ts index 4f7fac564e..72350d2002 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/bundle-extension-initializer.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/bundle-extension-initializer.ts @@ -1,21 +1,27 @@ -import type { ManifestBundle } from './types.js'; +import type { ManifestBase, ManifestBundle } from './types.js'; import { loadExtension } from './load-extension.function.js'; import { UmbExtensionRegistry } from './registry/extension.registry.js'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; export class UmbBundleExtensionInitializer { - #host; #extensionRegistry; #bundleMap = new Map(); constructor(host: UmbControllerHostElement, extensionRegistry: UmbExtensionRegistry) { - this.#host = host; this.#extensionRegistry = extensionRegistry; extensionRegistry.extensionsOfType('bundle').subscribe((bundles) => { + // Unregister removed bundles: + this.#bundleMap.forEach((existingBundle) => { + if (!bundles.find((b) => b.alias === existingBundle.alias)) { + this.unregisterBundle(existingBundle); + this.#bundleMap.delete(existingBundle.alias); + } + }); + + // Register new bundles: bundles.forEach((bundle) => { if (this.#bundleMap.has(bundle.alias)) return; this.#bundleMap.set(bundle.alias, bundle); - // TODO: Should we unInit a entry point if is removed? this.instantiateBundle(bundle); }); }); @@ -36,4 +42,20 @@ export class UmbBundleExtensionInitializer { }); } } + + async unregisterBundle(manifest: ManifestBundle) { + const js = await loadExtension(manifest); + + if (js) { + Object.keys(js).forEach((key) => { + const value = js[key]; + + if (Array.isArray(value)) { + this.#extensionRegistry.unregisterMany(value.map((v) => v.alias)); + } else if (typeof value === 'object') { + this.#extensionRegistry.unregister((value as ManifestBase).alias); + } + }); + } + } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point-extension-initializer.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point-extension-initializer.ts index 613f404790..fb1558962c 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point-extension-initializer.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point-extension-initializer.ts @@ -2,14 +2,14 @@ import type { ManifestEntryPoint } from './types.js'; import { hasInitExport } from './has-init-export.function.js'; import { loadExtension } from './load-extension.function.js'; import { UmbExtensionRegistry } from './registry/extension.registry.js'; -import { UmbElementMixinInterface } from '@umbraco-cms/backoffice/element-api'; +import { UmbElement } from '@umbraco-cms/backoffice/element-api'; export class UmbEntryPointExtensionInitializer { #host; #extensionRegistry; #entryPointMap = new Map(); - constructor(host: UmbElementMixinInterface, extensionRegistry: UmbExtensionRegistry) { + constructor(host: UmbElement, extensionRegistry: UmbExtensionRegistry) { this.#host = host; this.#extensionRegistry = extensionRegistry; extensionRegistry.extensionsOfType('entryPoint').subscribe((entryPoints) => { diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/umb-lifecycle.interface.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point.interface.ts similarity index 56% rename from src/Umbraco.Web.UI.Client/src/libs/extension-api/umb-lifecycle.interface.ts rename to src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point.interface.ts index 3d7d8ef66c..3574afc86d 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/umb-lifecycle.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point.interface.ts @@ -1,11 +1,8 @@ import type { UmbExtensionRegistry } from './registry/extension.registry.js'; import { ManifestBase } from './types.js'; -import type { UmbElementMixinInterface } from '@umbraco-cms/backoffice/element-api'; +import type { UmbElement } from '@umbraco-cms/backoffice/element-api'; -export type UmbEntryPointOnInit = ( - host: UmbElementMixinInterface, - extensionRegistry: UmbExtensionRegistry -) => void; +export type UmbEntryPointOnInit = (host: UmbElement, extensionRegistry: UmbExtensionRegistry) => void; /** * Interface containing supported life-cycle functions for ESModule entry points diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/has-init-export.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/has-init-export.function.ts index 585b76ee99..21107aa898 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/has-init-export.function.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/has-init-export.function.ts @@ -1,4 +1,4 @@ -import type { UmbEntryPointModule } from './umb-lifecycle.interface.js'; +import type { UmbEntryPointModule } from './entry-point.interface.js'; /** * Validate if an ESModule exports a known init function called 'onInit' diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/index.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/index.ts index 53457d7459..8764b44953 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/index.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/index.ts @@ -9,4 +9,4 @@ export * from './load-extension.function.js'; export * from './registry/extension.registry.js'; export * from './type-guards/index.js'; export * from './types.js'; -export * from './umb-lifecycle.interface.js'; +export * from './entry-point.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts index 3901cab6ab..9b3e908027 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts @@ -55,7 +55,11 @@ describe('UmbExtensionRegistry', () => { it('should register an extension', () => { const registeredExtensions = extensionRegistry['_extensions'].getValue(); expect(registeredExtensions).to.have.lengthOf(4); - expect(registeredExtensions?.[0]?.name).to.eq('test-section-1'); + expect(registeredExtensions?.[0]?.alias).to.eq('Umb.Test.Section.1'); + }); + + it('should say that an extension is registered', () => { + expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true; }); it('should get an extension by alias', (done) => { @@ -96,7 +100,7 @@ describe('UmbExtensionRegistry', () => { .unsubscribe(); }); - it('Observable only trigged when changes made to the scope of it', (done) => { + it('should only trigger observable when changes made to the scope of it', (done) => { let amountOfTimesTriggered = -1; let lastAmount = 0; @@ -218,6 +222,10 @@ describe('UmbExtensionRegistry with kinds', () => { manifests.forEach((manifest) => extensionRegistry.register(manifest)); }); + it('should say that an extension kind is registered', () => { + expect(extensionRegistry.isRegistered('Umb.Test.Kind')).to.be.true; + }); + it('should merge with kinds', (done) => { extensionRegistry .extensionsOfType('section') @@ -225,6 +233,7 @@ describe('UmbExtensionRegistry with kinds', () => { expect(extensions).to.have.lengthOf(3); expect(extensions?.[0]?.elementName).to.not.eq('my-kind-element'); expect(extensions?.[1]?.alias).to.eq('Umb.Test.Section.3'); + expect(extensions?.[1]?.meta.label).to.eq('Test Section 3'); expect(extensions?.[1]?.elementName).to.eq('my-kind-element'); expect(extensions?.[2]?.alias).to.eq('Umb.Test.Section.1'); expect(extensions?.[2]?.elementName).to.eq('my-kind-element'); @@ -233,4 +242,49 @@ describe('UmbExtensionRegistry with kinds', () => { }) .unsubscribe(); }); + + it('should update extensions using kinds, when a kind appears', (done) => { + let amountOfTimesTriggered = -1; + + extensionRegistry.unregister('Umb.Test.Kind'); + + extensionRegistry + .extensionsOfType('section') + .subscribe((extensions) => { + amountOfTimesTriggered++; + expect(extensions).to.have.lengthOf(3); + + if (amountOfTimesTriggered === 0) { + expect(extensions?.[2]?.meta.label).to.be.undefined; + expect(extensionRegistry.isRegistered('Umb.Test.Kind')).to.be.false; + extensionRegistry.register(manifests[0]); // Registration of the kind again. + expect(extensionRegistry.isRegistered('Umb.Test.Kind')).to.be.true; + } else if (amountOfTimesTriggered === 1) { + expect(extensions?.[2]?.meta.label).to.eq('my-kind-meta-label'); + done(); + } + }) + .unsubscribe(); + }); + + it('should update extensions using kinds, when a kind is removed', (done) => { + let amountOfTimesTriggered = -1; + + extensionRegistry + .extensionsOfType('section') + .subscribe((extensions) => { + amountOfTimesTriggered++; + expect(extensions).to.have.lengthOf(3); + + if (amountOfTimesTriggered === 0) { + expect(extensions?.[2]?.meta.label).to.not.be.undefined; + extensionRegistry.unregister('Umb.Test.Kind'); + expect(extensionRegistry.isRegistered('Umb.Test.Kind')).to.be.false; + } else if (amountOfTimesTriggered === 1) { + expect(extensions?.[2]?.meta.label).to.be.undefined; + done(); + } + }) + .unsubscribe(); + }); }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts index 107dbcfd41..96f5251c8f 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts @@ -1,13 +1,8 @@ import type { ManifestTypeMap, ManifestBase, SpecificManifestTypeOrManifestBase, ManifestKind } from '../types.js'; -import { - BehaviorSubject, - map, - Observable, - distinctUntilChanged, - combineLatest, -} from '@umbraco-cms/backoffice/external/rxjs'; +import { UmbBasicState } from '@umbraco-cms/backoffice/observable-api'; +import { map, Observable, distinctUntilChanged, combineLatest } from '@umbraco-cms/backoffice/external/rxjs'; -function extensionArrayMemoization( +function extensionArrayMemoization>( previousValue: Array, currentValue: Array ): boolean { @@ -22,6 +17,34 @@ function extensionArrayMemoization( return true; } +// Note: Keeping the memoization in two separate function, for performance concern. +function extensionAndKindMatchArrayMemoization & { isMatchedWithKind?: boolean }>( + previousValue: Array, + currentValue: Array +): boolean { + // If length is different, data is different: + if (previousValue.length !== currentValue.length) { + return false; + } + // previousValue has an alias that is not present in currentValue: + /* !! This is covered by the test below: + if (previousValue.find((p) => !currentValue.find((c) => c.alias === p.alias))) { + return false; + }*/ + // if previousValue has different meta values: + if ( + currentValue.find((newValue: T) => { + const oldValue = previousValue.find((c) => c.alias === newValue.alias); + // First check if we found a previous value, matching this alias. + // Then checking isMatchedWithKind, as this is much more performant than checking the whole object. (I assume the only change happening to an extension is the match with a kind, we do not want to watch for other changes) + return oldValue === undefined || newValue.isMatchedWithKind !== oldValue.isMatchedWithKind; + }) + ) { + return false; + } + return true; +} + function extensionSingleMemoization( previousValue: T | undefined, currentValue: T | undefined @@ -40,11 +63,10 @@ export class UmbExtensionRegistry< > { readonly MANIFEST_TYPES: ManifestTypes = undefined as never; - // TODO: Use UniqueBehaviorSubject, as we don't want someone to edit data of extensions. - private _extensions = new BehaviorSubject>([]); + private _extensions = new UmbBasicState>([]); public readonly extensions = this._extensions.asObservable(); - private _kinds = new BehaviorSubject>>([]); + private _kinds = new UmbBasicState>>([]); public readonly kinds = this._kinds.asObservable(); defineKind(kind: ManifestKind) { @@ -84,22 +106,28 @@ export class UmbExtensionRegistry< manifests.forEach((manifest) => this.register(manifest)); } + unregisterMany(aliases: Array): void { + aliases.forEach((alias) => this.unregister(alias)); + } + unregister(alias: string): void { - const oldExtensionsValues = this._extensions.getValue(); - const newExtensionsValues = oldExtensionsValues.filter((extension) => extension.alias !== alias); - - // TODO: Maybe its not needed to fire an console.error. as you might want to call this method without needing to check the existence first. - if (oldExtensionsValues.length === newExtensionsValues.length) { - console.error(`Unable to unregister extension with alias ${alias}`); - return; - } + const newKindsValues = this._kinds.getValue().filter((kind) => kind.alias !== alias); + const newExtensionsValues = this._extensions.getValue().filter((extension) => extension.alias !== alias); + this._kinds.next(newKindsValues); this._extensions.next(newExtensionsValues); } isRegistered(alias: string): boolean { - const values = this._extensions.getValue(); - return values.some((ext) => ext.alias === alias); + if (this._extensions.getValue().find((ext) => ext.alias === alias)) { + return true; + } + + if (this._kinds.getValue().find((ext) => ext.alias === alias)) { + return true; + } + + return false; } /* @@ -155,7 +183,7 @@ export class UmbExtensionRegistry< if (ext) { const baseManifest = kinds.find((kind) => kind.matchKind === ext.kind)?.manifest; if (baseManifest) { - const merged = { ...baseManifest, ...ext } as any; + const merged = { isMatchedWithKind: true, ...baseManifest, ...ext } as any; if ((baseManifest as any).meta) { merged.meta = { ...(baseManifest as any).meta, ...(ext as any).meta }; } @@ -179,7 +207,7 @@ export class UmbExtensionRegistry< // Specific Extension Meta merge (does not merge conditions) const baseManifest = kinds.find((kind) => kind.matchKind === ext.kind)?.manifest; if (baseManifest) { - const merged = { ...baseManifest, ...ext } as any; + const merged = { isMatchedWithKind: true, ...baseManifest, ...ext } as any; if ((baseManifest as any).meta) { merged.meta = { ...(baseManifest as any).meta, ...(ext as any).meta }; } @@ -189,7 +217,7 @@ export class UmbExtensionRegistry< }) .sort(sortExtensions) ), - distinctUntilChanged(extensionArrayMemoization) + distinctUntilChanged(extensionAndKindMatchArrayMemoization) ) as Observable>; } @@ -204,7 +232,7 @@ export class UmbExtensionRegistry< if (ext) { const baseManifest = kinds.find((kind) => kind.matchKind === ext.kind)?.manifest; if (baseManifest) { - const merged = { ...baseManifest, ...ext } as any; + const merged = { isMatchedWithKind: true, ...baseManifest, ...ext } as any; if ((baseManifest as any).meta) { merged.meta = { ...(baseManifest as any).meta, ...(ext as any).meta }; } @@ -215,7 +243,7 @@ export class UmbExtensionRegistry< }) .sort(sortExtensions) ), - distinctUntilChanged(extensionArrayMemoization) + distinctUntilChanged(extensionAndKindMatchArrayMemoization) ) as Observable>; } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types.ts index fda4093480..c8545bb46d 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types.ts @@ -1,12 +1,11 @@ -import type { UmbExtensionRegistry } from './registry/extension.registry.js'; +import { UmbEntryPointModule } from './entry-point.interface.js'; import { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type HTMLElementConstructor = new (...args: any[]) => T; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export type ClassConstructor = new (...args: any[]) => T; +export type ClassConstructor = new (...args: any[]) => T; export type ManifestTypeMap = { [Manifest in ManifestTypes as Manifest['type']]: Manifest; @@ -156,10 +155,7 @@ export interface ManifestWithMeta extends ManifestBase { * This type of extension gives full control and will simply load the specified JS file * You could have custom logic to decide which extensions to load/register by using extensionRegistry */ -export interface ManifestEntryPoint - extends ManifestWithLoader<{ - onInit: (host: UmbControllerHostElement, extensionApi: UmbBackofficeExtensionRegistry) => void; - }> { +export interface ManifestEntryPoint extends ManifestWithLoader { type: 'entryPoint'; /** diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/class-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/class-state.ts index 6196c31822..3211f757df 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/class-state.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/class-state.ts @@ -1,7 +1,7 @@ import { BehaviorSubject } from '@umbraco-cms/backoffice/external/rxjs'; interface UmbClassStateData { - equal(otherClass: UmbClassStateData): boolean; + equal(otherClass: UmbClassStateData | undefined): boolean; } /** @@ -10,7 +10,7 @@ interface UmbClassStateData { * @extends {BehaviorSubject} * @description - A RxJS BehaviorSubject which can hold class instance which has a equal method to compare in coming instances for changes. */ -export class UmbClassState extends BehaviorSubject { +export class UmbClassState extends BehaviorSubject { constructor(initialData: T) { super(initialData); } diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts index d682afcafc..74fe0f347d 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts @@ -1,14 +1,19 @@ import { UmbObserver } from './observer.js'; import { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbControllerInterface, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbController, UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -export class UmbObserverController extends UmbObserver implements UmbControllerInterface { - _alias?: string; - public get unique() { +export class UmbObserverController extends UmbObserver implements UmbController { + _alias?: UmbControllerAlias; + public get controllerAlias() { return this._alias; } - constructor(host: UmbControllerHostElement, source: Observable, callback: (_value: T) => void, alias?: string) { + constructor( + host: UmbControllerHost, + source: Observable, + callback: (_value: T) => void, + alias?: UmbControllerAlias + ) { super(source, callback); this._alias = alias; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/users.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/users.data.ts index beda25493a..dadb4faae1 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/users.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/users.data.ts @@ -1,5 +1,5 @@ -import { UmbLoggedInUser } from '@umbraco-cms/backoffice/auth'; import { UmbData } from './data.js'; +import { UmbLoggedInUser } from '@umbraco-cms/backoffice/auth'; import { PagedUserResponseModel, UserResponseModel, UserStateModel } from '@umbraco-cms/backoffice/backend-api'; // Temp mocked database diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/performance-profiling.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/performance-profiling.handlers.ts index ca47df9754..331dbc8bfe 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/performance-profiling.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/performance-profiling.handlers.ts @@ -11,4 +11,11 @@ export const handlers = [ ctx.json({ enabled: true }) ); }), + + rest.put(umbracoPath('/profiling/status'), (_req, res, ctx) => { + return res( + // Respond with a 200 status code + ctx.status(200) + ); + }), ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/data-type/data-type-property-collection.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/data-type/data-type-property-collection.class.ts index 90eed2632f..f0235b82e9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/data-type/data-type-property-collection.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/data-type/data-type-property-collection.class.ts @@ -1,11 +1,12 @@ +import { type UmbDataTypeConfigProperty, type UmbDataTypeConfig } from '../../property-editors/index.js'; import { DataTypePropertyPresentationModel } from '@umbraco-cms/backoffice/backend-api'; /** * Extends Array to add utility functions for accessing data type properties * by alias, returning either the value or the complete DataTypePropertyPresentationModel object */ -export class UmbDataTypePropertyCollection extends Array { - constructor(args: Array = []) { +export class UmbDataTypeConfigCollection extends Array { + constructor(args: UmbDataTypeConfig) { super(...args); } static get [Symbol.species](): ArrayConstructor { @@ -43,4 +44,15 @@ export class UmbDataTypePropertyCollection extends Array { return Object.fromEntries(this.map((x) => [x.alias, x.value])); } + + equal(other: UmbDataTypeConfigCollection | undefined): boolean { + if (this.length !== other?.length) { + return false; + } + + return this.every((x) => { + const otherProperty = x.alias ? other.getByAlias(x.alias!) : undefined; + return otherProperty && x.value === otherProperty.value; + }); + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts index bebd992be3..0aefbaa750 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts @@ -7,7 +7,7 @@ import { renderEditor, type tinymce } from '@umbraco-cms/backoffice/external/tin import { UMB_AUTH, UmbLoggedInUser } from '@umbraco-cms/backoffice/auth'; import { TinyMcePluginArguments, - UmbDataTypePropertyCollection, + UmbDataTypeConfigCollection, UmbTinyMcePluginBase, } from '@umbraco-cms/backoffice/components'; import { ClassConstructor, hasDefaultExport, loadExtension } from '@umbraco-cms/backoffice/extension-api'; @@ -29,8 +29,8 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; // TODO => integrate macro picker, update stylesheet fetch when backend CLI exists (ref tinymce.service.js in existing backoffice) @customElement('umb-input-tiny-mce') export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { - @property({ type: Object }) - configuration?: UmbDataTypePropertyCollection; + @property({ attribute: false }) + configuration?: UmbDataTypeConfigCollection; @state() private _tinyConfig: tinymce.RawEditorOptions = {}; @@ -43,7 +43,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { #editorRef?: tinymce.Editor | null = null; protected getFormElement() { - return undefined; + return this._editorElement?.querySelector('iframe') ?? undefined; } @query('#editor', true) @@ -80,6 +80,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { super.disconnectedCallback(); if (this.#editorRef) { + // TODO: Test if there is any problems with destroying the RTE here, but not initializing on connectedCallback. (firstUpdated is only called first time the element is rendered, not when it is reconnected) this.#editorRef.destroy(); } } @@ -104,6 +105,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { async #setTinyConfig() { // create an object by merging the configuration onto the fallback config + // TODO: Seems like a too tight coupling between DataTypeConfigCollection and TinyMceConfig, I would love it begin more explicit what we take from DataTypeConfigCollection and parse on, but I understand that this gives some flexibility. Is this flexibility on purpose? const configurationOptions: Record = { ...defaultFallbackConfig, ...(this.configuration ? this.configuration?.toObject() : {}), @@ -203,7 +205,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { // Plugins require a reference to the current editor as a param, so can not // be instantiated until we have an editor for (const plugin of this.#plugins) { - new plugin({ host: this, editor }); + new plugin({ host: this, editor }); } // define keyboard shortcuts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/tiny-mce-plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/tiny-mce-plugin.ts index 3f8f7715be..fb26439393 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/tiny-mce-plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/tiny-mce-plugin.ts @@ -1,10 +1,10 @@ -import type { UmbDataTypePropertyCollection, UmbInputTinyMceElement } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection, UmbInputTinyMceElement } from '@umbraco-cms/backoffice/components'; import type { tinymce } from '@umbraco-cms/backoffice/external/tinymce'; export class UmbTinyMcePluginBase { host: UmbInputTinyMceElement; editor: tinymce.Editor; - configuration?: UmbDataTypePropertyCollection; + configuration?: UmbDataTypeConfigCollection; constructor(arg: TinyMcePluginArguments) { this.host = arg.host; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts index ff93a45bb2..46a403c109 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts @@ -1,13 +1,10 @@ +import { UmbDataTypeConfig, type UmbDataTypeConfigProperty } from '../../property-editors/index.js'; import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { css, html, ifDefined, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbDataTypeRepository } from '@umbraco-cms/backoffice/data-type'; import { UmbDocumentWorkspaceContext } from '@umbraco-cms/backoffice/document'; import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { - DataTypeResponseModel, - DataTypePropertyPresentationModel, - PropertyTypeModelBaseModel, -} from '@umbraco-cms/backoffice/backend-api'; +import type { DataTypeResponseModel, PropertyTypeModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; import { UMB_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; @@ -32,7 +29,7 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { private _propertyEditorUiAlias?: string; @state() - private _dataTypeData: DataTypePropertyPresentationModel[] = []; + private _dataTypeData?: UmbDataTypeConfig; private _dataTypeRepository: UmbDataTypeRepository = new UmbDataTypeRepository(this); private _dataTypeObserver?: UmbObserverController; @@ -92,7 +89,7 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { this._dataTypeObserver = this.observe( await this._dataTypeRepository.byId(dataTypeId), (dataType) => { - this._dataTypeData = dataType?.values || []; + this._dataTypeData = dataType?.values; this._propertyEditorUiAlias = dataType?.propertyEditorUiAlias || undefined; // If there is no UI, we will look up the Property editor model to find the default UI alias: if (!this._propertyEditorUiAlias && dataType?.propertyEditorAlias) { @@ -102,7 +99,7 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { (extension) => { if (!extension) return; this._propertyEditorUiAlias = extension?.meta.defaultPropertyEditorUiAlias; - this.removeControllerByUnique('_observePropertyEditorSchema'); + this.removeControllerByAlias('_observePropertyEditorSchema'); }, '_observePropertyEditorSchema' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-structure-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-structure-manager.class.ts index fd6f0c6b1a..814da5994c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-structure-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-structure-manager.class.ts @@ -6,7 +6,7 @@ import { PropertyTypeModelBaseModel, DocumentTypeResponseModel, } from '@umbraco-cms/backoffice/backend-api'; -import { UmbControllerHostElement, UmbControllerInterface } from '@umbraco-cms/backoffice/controller-api'; +import { UmbControllerHostElement, UmbController } from '@umbraco-cms/backoffice/controller-api'; import { UmbArrayState, UmbObserverController, @@ -29,7 +29,7 @@ export class UmbContentTypePropertyStructureManager(); + #documentTypeObservers = new Array(); #documentTypes = new UmbArrayState([], (x) => x.id); readonly documentTypes = this.#documentTypes.asObservable(); private readonly _documentTypeContainers = this.#documentTypes.getObservablePart((x) => diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/class-extensions-initializer.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/class-extensions-initializer.ts new file mode 100644 index 0000000000..4874bb838d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/class-extensions-initializer.ts @@ -0,0 +1,37 @@ +import { createExtensionClass } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbBaseController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; + +/** + * Initializes extension classes for a host element. + * Extension class will be given one argument, the host element. + * + * @param host The host element to initialize extension classes for. + * @param extensionTypes The extension types(strings) to initialize. + * + */ +export class UmbClassExtensionsInitializer extends UmbBaseController { + #extensionMap = new Map(); + + constructor(host: UmbControllerHostElement, extensionTypes: Array) { + super(host); + + this.observe(umbExtensionsRegistry.extensionsOfTypes(extensionTypes), (extensions) => { + if (!extensions) return; + + extensions.forEach((extension) => { + if (this.#extensionMap.has(extension.alias)) return; + + // Instantiate and provide extension JS class. For Context API the classes provide them selfs when the class instantiates. + this.#extensionMap.set(extension.alias, createExtensionClass(extension, [this._host])); + }); + }); + } + + public destroy(): void { + this.#extensionMap.forEach((extension) => { + extension.destroy(); + }); + this.#extensionMap.clear(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/extension-class-initializer.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/extension-class-initializer.ts index 43b314088a..27fe168e8c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/extension-class-initializer.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/extension-class-initializer.ts @@ -1,22 +1,21 @@ import type { ManifestTypes } from './models/index.js'; import { umbExtensionsRegistry } from './registry.js'; -import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; import { createExtensionClass, ManifestBase, ManifestClass, SpecificManifestTypeOrManifestBase, } from '@umbraco-cms/backoffice/extension-api'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBaseController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; export class UmbExtensionClassInitializer< ExtensionType extends string = string, ExtensionManifest extends ManifestBase = SpecificManifestTypeOrManifestBase, ExtensionClassInterface = ExtensionManifest extends ManifestClass ? ExtensionManifest['CLASS_TYPE'] : unknown -> { - #observable; +> extends UmbBaseController { #currentPromise?: Promise; #currentPromiseResolver?: (value: ExtensionClassInterface | undefined) => void; + #currentClass?: ExtensionClassInterface; constructor( host: UmbControllerHostElement, @@ -24,16 +23,20 @@ export class UmbExtensionClassInitializer< extensionAlias: string, callback: (extensionClass: ExtensionClassInterface | undefined) => void ) { + super(host); const source = umbExtensionsRegistry.getByTypeAndAlias(extensionType, extensionAlias); //TODO: The promise can probably be done in a cleaner way. - this.#observable = new UmbObserverController(host, source, async (manifest) => { + this.observe(source, async (manifest) => { if (!manifest) return; try { - const initializedClass = await createExtensionClass(manifest, [host]); - callback(initializedClass); + // Destroy the previous class if it exists, and if destroy method is an method on the class. + (this.#currentClass as any)?.destroy?.(); + + this.#currentClass = await createExtensionClass(manifest, [host]); + callback(this.#currentClass); if (this.#currentPromiseResolver) { - this.#currentPromiseResolver(initializedClass); + this.#currentPromiseResolver(this.#currentClass); this.#currentPromise = undefined; this.#currentPromiseResolver = undefined; } @@ -49,4 +52,10 @@ export class UmbExtensionClassInitializer< }); return this.#currentPromise; } + + public destroy(): void { + super.destroy(); + // Destroy the current class if it exists, and if destroy method is an method on the class. + (this.#currentClass as any)?.destroy?.(); + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts index d2e414594e..c3251ec168 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts @@ -2,3 +2,4 @@ export * from './interfaces/index.js'; export * from './models/index.js'; export * from './registry.js'; export * from './extension-class-initializer.js'; +export * from './class-extensions-initializer.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/interfaces/property-editor-ui-extension-element.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/interfaces/property-editor-ui-extension-element.interface.ts index dd8f391cbd..09d40521dc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/interfaces/property-editor-ui-extension-element.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/interfaces/property-editor-ui-extension-element.interface.ts @@ -1,6 +1,6 @@ -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; export interface UmbPropertyEditorExtensionElement extends HTMLElement { value: unknown; - config?: UmbDataTypePropertyCollection; + config?: UmbDataTypeConfigCollection; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/global-context.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/global-context.model.ts new file mode 100644 index 0000000000..cc6f8f234d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/global-context.model.ts @@ -0,0 +1,5 @@ +import type { ManifestClass } from '@umbraco-cms/backoffice/extension-api'; + +export interface ManifestGlobalContext extends ManifestClass { + type: 'globalContext'; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts index 6d838deeba..7c2d350740 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts @@ -4,6 +4,7 @@ import type { ManifestDashboardCollection } from './dashboard-collection.model.j import type { ManifestEntityAction } from './entity-action.model.js'; import type { ManifestEntityBulkAction } from './entity-bulk-action.model.js'; import type { ManifestExternalLoginProvider } from './external-login-provider.model.js'; +import type { ManifestGlobalContext } from './global-context.model.js'; import type { ManifestHeaderApp, ManifestHeaderAppButtonKind } from './header-app.model.js'; import type { ManifestHealthCheck } from './health-check.model.js'; import type { ManifestMenu } from './menu.model.js'; @@ -34,6 +35,7 @@ export * from './dashboard.model.js'; export * from './entity-action.model.js'; export * from './entity-bulk-action.model.js'; export * from './external-login-provider.model.js'; +export * from './global-context.model.js'; export * from './header-app.model.js'; export * from './health-check.model.js'; export * from './menu-item.model.js'; @@ -66,6 +68,7 @@ export type ManifestTypes = | ManifestEntityBulkAction | ManifestEntryPoint | ManifestExternalLoginProvider + | ManifestGlobalContext | ManifestHeaderApp | ManifestHeaderAppButtonKind | ManifestHealthCheck diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/property-editor.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/property-editor.model.ts index 5b76646ad0..70a0ef3316 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/property-editor.model.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/property-editor.model.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorExtensionElement } from '../interfaces/index.js'; -import { DataTypePropertyPresentationModel } from '@umbraco-cms/backoffice/backend-api'; +import { type UmbDataTypeConfig } from '../../property-editors/index.js'; import type { ManifestElement, ManifestBase } from '@umbraco-cms/backoffice/extension-api'; export interface ManifestPropertyEditorUi extends ManifestElement { @@ -39,7 +39,7 @@ export interface PropertyEditorConfigProperty { description?: string; alias: string; propertyEditorUiAlias: string; - config?: Array; + config?: UmbDataTypeConfig; } export interface PropertyEditorConfigDefaultData { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/index.ts index 8e276d67ee..408a87932b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/index.ts @@ -2,16 +2,20 @@ import { UmbBackofficeNotificationContainerElement, UmbBackofficeModalContainerE import { manifests as debugManifests } from './debug/manifests.js'; import { manifests as propertyActionManifests } from './property-actions/manifests.js'; import { manifests as propertyEditorManifests } from './property-editors/manifests.js'; -import { manifests as tinyMcePluginManifests } from './property-editors/uis/tiny-mce/plugins/manifests.js'; +import { manifests as tinyMcePluginManifests } from './property-editors/uis/tiny-mce/plugins/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import { manifests as modalManifests } from './modal/common/manifests.js'; -import { UmbStoreExtensionInitializer } from './store/index.js'; +import { manifests as themeManifests } from './themes/manifests.js'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; import { UmbModalManagerContext, UMB_MODAL_MANAGER_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/modal'; import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; -import { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry'; +import { + ManifestTypes, + UmbBackofficeManifestKind, + UmbClassExtensionsInitializer, +} from '@umbraco-cms/backoffice/extension-registry'; export * from './action/index.js'; export * from './collection/index.js'; @@ -43,10 +47,11 @@ const manifests: Array = [ ...tinyMcePluginManifests, ...workspaceManifests, ...modalManifests, + ...themeManifests, ]; export const onInit: UmbEntryPointOnInit = (host, extensionRegistry) => { - new UmbStoreExtensionInitializer(host); + new UmbClassExtensionsInitializer(host, ['globalContext', 'store', 'treeStore', 'itemStore']); extensionRegistry.registerMany(manifests); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-context.ts index 1eb89cc8c8..03041aa9e3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-context.ts @@ -11,7 +11,7 @@ import { BehaviorSubject } from '@umbraco-cms/backoffice/external/rxjs'; import { ManifestModal, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbRouterSlotElement } from '@umbraco-cms/backoffice/router'; import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api'; -import type { UmbControllerHostElement, UmbControllerInterface } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement, UmbController } from '@umbraco-cms/backoffice/controller-api'; import { UmbId } from '@umbraco-cms/backoffice/id'; import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; import { UmbContextProvider, UmbContextToken } from '@umbraco-cms/backoffice/context-api'; @@ -41,9 +41,7 @@ type OptionalSubmitArgumentIfUndefined = T extends undefined }; // TODO: consider splitting this into two separate handlers -export class UmbModalContextClass - implements UmbControllerInterface -{ +export class UmbModalContextClass implements UmbController { #host: UmbControllerHostElement; #submitPromise: Promise; @@ -63,7 +61,7 @@ export class UmbModalContextClass extends UmbModalRouteRegistration - implements UmbControllerInterface + implements UmbController { //#host: UmbControllerHostInterface; #init; @@ -19,7 +19,7 @@ export class UmbModalRouteRegistrationControllerumb-property-editor-ui-block-list`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/checkbox-list/property-editor-ui-checkbox-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/checkbox-list/property-editor-ui-checkbox-list.element.ts index 0695413cf1..b0c7fb6ffc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/checkbox-list/property-editor-ui-checkbox-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/checkbox-list/property-editor-ui-checkbox-list.element.ts @@ -3,7 +3,7 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/ex import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-checkbox-list @@ -19,8 +19,9 @@ export class UmbPropertyEditorUICheckboxListElement extends UmbLitElement implem this.#value = value ?? []; } - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + if (!config) return; const listData: Record | undefined = config.getValueByAlias('items'); if (!listData) return; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts index 24c630ca8a..c322797111 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts @@ -3,7 +3,7 @@ import { UUITextStyles, UUIColorSwatchesEvent } from '@umbraco-cms/backoffice/ex import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { UmbSwatchDetails } from '@umbraco-cms/backoffice/models'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-color-picker @@ -21,10 +21,10 @@ export class UmbPropertyEditorUIColorPickerElement extends UmbLitElement impleme @state() private _swatches: UmbSwatchDetails[] = []; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this._showLabels = config.getValueByAlias('useLabel') ?? this.#defaultShowLabels; - this._swatches = config.getValueByAlias('items') ?? []; + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + this._showLabels = config?.getValueByAlias('useLabel') ?? this.#defaultShowLabels; + this._swatches = config?.getValueByAlias('items') ?? []; } private _onChange(event: UUIColorSwatchesEvent) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.element.ts index 5000ed07a2..53ce65b1f0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.element.ts @@ -3,7 +3,7 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/ex import { UUITextStyles, InputType } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-date-picker @@ -53,8 +53,9 @@ export class UmbPropertyEditorUIDatePickerElement extends UmbLitElement implemen private _offsetTime?: boolean; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + if (!config) return; const oldVal = this._inputType; // Format string prevalue/config diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.stories.ts index 35dccfc20c..f9f1e29af1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.stories.ts @@ -3,14 +3,14 @@ import type { UmbPropertyEditorUIDatePickerElement } from './property-editor-ui- import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-date-picker.element.js'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; export default { title: 'Property Editor UIs/Date Picker', component: 'umb-property-editor-ui-date-picker', id: 'umb-property-editor-ui-date-picker', args: { - config: new UmbDataTypePropertyCollection([ + config: new UmbDataTypeConfigCollection([ { alias: 'format', value: 'YYYY-MM-DD HH:mm:ss', @@ -31,7 +31,7 @@ WithDateValue.args = { export const WithFormat = Template.bind({}); WithFormat.args = { - config: new UmbDataTypePropertyCollection([ + config: new UmbDataTypeConfigCollection([ { alias: 'format', value: 'dd/MM/yyyy HH:mm:ss', @@ -41,7 +41,7 @@ WithFormat.args = { export const Timeframe = Template.bind({}); Timeframe.args = { - config: new UmbDataTypePropertyCollection([ + config: new UmbDataTypeConfigCollection([ { alias: 'format', value: 'dd/MM/yyyy HH:mm:ss', @@ -59,7 +59,7 @@ Timeframe.args = { export const TimeOnly = Template.bind({}); TimeOnly.args = { - config: new UmbDataTypePropertyCollection([ + config: new UmbDataTypeConfigCollection([ { alias: 'format', value: 'HH:mm:ss', @@ -69,7 +69,7 @@ TimeOnly.args = { export const DateOnly = Template.bind({}); DateOnly.args = { - config: new UmbDataTypePropertyCollection([ + config: new UmbDataTypeConfigCollection([ { alias: 'format', value: 'dd/MM/yyyy', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.test.ts index dd4a637fc2..bf923029a6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/date-picker/property-editor-ui-date-picker.test.ts @@ -2,7 +2,7 @@ import { expect, fixture, html } from '@open-wc/testing'; import { UmbInputDateElement } from '../../../components/input-date/input-date.element.js'; import { UmbPropertyEditorUIDatePickerElement } from './property-editor-ui-date-picker.element.js'; import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; describe('UmbPropertyEditorUIDatePickerElement', () => { let element: UmbPropertyEditorUIDatePickerElement; @@ -26,13 +26,13 @@ describe('UmbPropertyEditorUIDatePickerElement', () => { }); it('should show a type=date field if the format only contains a date', async () => { - element.config = new UmbDataTypePropertyCollection([{ alias: 'format', value: 'YYYY-MM-dd' }]); + element.config = new UmbDataTypeConfigCollection([{ alias: 'format', value: 'YYYY-MM-dd' }]); await element.updateComplete; expect(inputElement.type).to.equal('date'); }); it('should show a type=time field if the format only contains a time', async () => { - element.config = new UmbDataTypePropertyCollection([{ alias: 'format', value: 'HH:mm' }]); + element.config = new UmbDataTypeConfigCollection([{ alias: 'format', value: 'HH:mm' }]); await element.updateComplete; expect(inputElement.type).to.equal('time'); }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/dropdown/property-editor-ui-dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/dropdown/property-editor-ui-dropdown.element.ts index 56ef27fe24..ded84ee76d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/dropdown/property-editor-ui-dropdown.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/dropdown/property-editor-ui-dropdown.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-dropdown @@ -13,7 +13,7 @@ export class UmbPropertyEditorUIDropdownElement extends UmbLitElement implements value = ''; @property({ attribute: false }) - public config?: UmbDataTypePropertyCollection; + public config?: UmbDataTypeConfigCollection; render() { return html`
umb-property-editor-ui-dropdown
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/eye-dropper/property-editor-ui-eye-dropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/eye-dropper/property-editor-ui-eye-dropper.element.ts index e13fb01b6b..feab89f6c2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/eye-dropper/property-editor-ui-eye-dropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/eye-dropper/property-editor-ui-eye-dropper.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/ex import { UUITextStyles, UUIColorPickerChangeEvent } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-eye-dropper @@ -20,10 +20,12 @@ export class UmbPropertyEditorUIEyeDropperElement extends UmbLitElement implemen @state() private _swatches: string[] = []; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this._opacity = config.getValueByAlias('showAlpha') ?? this.#defaultOpacity; - this._swatches = config.getValueByAlias('palette') ?? []; + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + if (config) { + this._opacity = config.getValueByAlias('showAlpha') ?? this.#defaultOpacity; + this._swatches = config.getValueByAlias('palette') ?? []; + } } private _onChange(event: UUIColorPickerChangeEvent) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/icon-picker/property-editor-ui-icon-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/icon-picker/property-editor-ui-icon-picker.element.ts index 0961109412..c37d42d8cd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/icon-picker/property-editor-ui-icon-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/icon-picker/property-editor-ui-icon-picker.element.ts @@ -7,7 +7,7 @@ import { UMB_ICON_PICKER_MODAL, } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-icon-picker @@ -17,8 +17,8 @@ export class UmbPropertyEditorUIIconPickerElement extends UmbLitElement implemen @property() value = ''; - @property({ type: Array, attribute: false }) - public config?: UmbDataTypePropertyCollection; + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; private _modalContext?: UmbModalManagerContext; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/image-cropper/property-editor-ui-image-cropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/image-cropper/property-editor-ui-image-cropper.element.ts index 32371aab92..7a15e6ce4a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/image-cropper/property-editor-ui-image-cropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/image-cropper/property-editor-ui-image-cropper.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-image-cropper @@ -12,8 +12,8 @@ export class UmbPropertyEditorUIImageCropperElement extends UmbLitElement implem @property() value = ''; - @property({ type: Array, attribute: false }) - public config?: UmbDataTypePropertyCollection; + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; render() { return html`
umb-property-editor-ui-image-cropper
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/image-crops-configuration/property-editor-ui-image-crops-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/image-crops-configuration/property-editor-ui-image-crops-configuration.element.ts index 36ee6883d4..a3e5318917 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/image-crops-configuration/property-editor-ui-image-crops-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/image-crops-configuration/property-editor-ui-image-crops-configuration.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-image-crops-configuration @@ -15,8 +15,8 @@ export class UmbPropertyEditorUIImageCropsConfigurationElement @property() value = ''; - @property({ type: Array, attribute: false }) - public config = new UmbDataTypePropertyCollection(); + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; render() { return html`
umb-property-editor-ui-image-crops-configuration
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/label/property-editor-ui-label.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/label/property-editor-ui-label.element.ts index 9aea140476..e088b3eb79 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/label/property-editor-ui-label.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/label/property-editor-ui-label.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-label @@ -15,8 +15,8 @@ export class UmbPropertyEditorUILabelElement extends UmbLitElement implements Um @property() description = ''; - @property({ type: Array, attribute: false }) - public config?: UmbDataTypePropertyCollection; + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; render() { return html`${this.value ?? ''}`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/markdown-editor/property-editor-ui-markdown-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/markdown-editor/property-editor-ui-markdown-editor.element.ts index 3a3e79e050..de2b862acc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/markdown-editor/property-editor-ui-markdown-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/markdown-editor/property-editor-ui-markdown-editor.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-markdown-editor @@ -15,8 +15,8 @@ export class UmbPropertyEditorUIMarkdownEditorElement @property() value = ''; - @property({ type: Array, attribute: false }) - public config = new UmbDataTypePropertyCollection(); + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; render() { return html`
umb-property-editor-ui-markdown-editor
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/media-picker/property-editor-ui-media-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/media-picker/property-editor-ui-media-picker.element.ts index 04004388f8..f37308e4c8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/media-picker/property-editor-ui-media-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/media-picker/property-editor-ui-media-picker.element.ts @@ -1,6 +1,6 @@ import { UmbInputMediaElement } from '../../../../media/media/components/input-media/input-media.element.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -19,9 +19,9 @@ export class UmbPropertyEditorUIMediaPickerElement extends UmbLitElement impleme this._value = value || []; } - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - const validationLimit = config.getByAlias('validationLimit'); + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + const validationLimit = config?.getByAlias('validationLimit'); if (!validationLimit) return; const minMax: Record = validationLimit.value; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/member-group-picker/property-editor-ui-member-group-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/member-group-picker/property-editor-ui-member-group-picker.element.ts index 02bcebf669..1d72bcb4e3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/member-group-picker/property-editor-ui-member-group-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/member-group-picker/property-editor-ui-member-group-picker.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-member-group-picker @@ -15,8 +15,8 @@ export class UmbPropertyEditorUIMemberGroupPickerElement @property() value = ''; - @property({ type: Array, attribute: false }) - public config = new UmbDataTypePropertyCollection(); + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; render() { return html`
umb-property-editor-ui-member-group-picker
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/member-picker/property-editor-ui-member-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/member-picker/property-editor-ui-member-picker.element.ts index 0a7fdd2f69..74946a89e1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/member-picker/property-editor-ui-member-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/member-picker/property-editor-ui-member-picker.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-member-picker @@ -12,8 +12,8 @@ export class UmbPropertyEditorUIMemberPickerElement extends UmbLitElement implem @property() value = ''; - @property({ type: Array, attribute: false }) - public config = new UmbDataTypePropertyCollection(); + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; render() { return html`
umb-property-editor-ui-member-picker
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts index 3e6c588658..f043fcf0e8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts @@ -6,7 +6,7 @@ import { UMB_WORKSPACE_PROPERTY_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/wo import { UmbLinkPickerLink } from '@umbraco-cms/backoffice/modal'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-multi-url-picker @@ -19,13 +19,13 @@ export class UmbPropertyEditorUIMultiUrlPickerElement @property({ type: Array }) value: UmbLinkPickerLink[] = []; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this._overlaySize = config.getValueByAlias('overlaySize'); - this._hideAnchor = config.getValueByAlias('hideAnchor'); - this._ignoreUserStartNodes = config.getValueByAlias('ignoreUserStartNodes'); - this._minNumber = config.getValueByAlias('minNumber'); - this._maxNumber = config.getValueByAlias('maxNumber'); + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + this._overlaySize = config?.getValueByAlias('overlaySize'); + this._hideAnchor = config?.getValueByAlias('hideAnchor'); + this._ignoreUserStartNodes = config?.getValueByAlias('ignoreUserStartNodes'); + this._minNumber = config?.getValueByAlias('minNumber'); + this._maxNumber = config?.getValueByAlias('maxNumber'); } @state() diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/multiple-text-string/property-editor-ui-multiple-text-string.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/multiple-text-string/property-editor-ui-multiple-text-string.element.ts index 9de4b73f06..6a21a9804c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/multiple-text-string/property-editor-ui-multiple-text-string.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/multiple-text-string/property-editor-ui-multiple-text-string.element.ts @@ -4,7 +4,7 @@ import { MultipleTextStringValue, } from './input-multiple-text-string/input-multiple-text-string.element.js'; import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/events'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -20,10 +20,10 @@ export class UmbPropertyEditorUIMultipleTextStringElement @property({ type: Array }) public value: MultipleTextStringValue = []; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this._limitMin = config.getValueByAlias('minNumber'); - this._limitMax = config.getValueByAlias('maxNumber'); + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + this._limitMin = config?.getValueByAlias('minNumber'); + this._limitMax = config?.getValueByAlias('maxNumber'); } /** diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/number-range/property-editor-ui-number-range.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/number-range/property-editor-ui-number-range.element.ts index 391b641a12..1ccb715d85 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/number-range/property-editor-ui-number-range.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/number-range/property-editor-ui-number-range.element.ts @@ -3,7 +3,7 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/ex import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; import '../../../components/input-number-range/input-number-range.element.js'; @@ -28,8 +28,8 @@ export class UmbPropertyEditorUINumberRangeElement extends UmbLitElement impleme this._maxValue = value?.max; } - @property({ type: Array, attribute: false }) - public config = new UmbDataTypePropertyCollection(); + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; private _onChange(event: CustomEvent) { this.value = { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/number/property-editor-ui-number.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/number/property-editor-ui-number.element.ts index 1b9b6ec51a..1eda8023b6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/number/property-editor-ui-number.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/number/property-editor-ui-number.element.ts @@ -2,7 +2,7 @@ import { css, html, customElement, property, state, ifDefined } from '@umbraco-c import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; @customElement('umb-property-editor-ui-number') export class UmbPropertyEditorUINumberElement extends UmbLitElement implements UmbPropertyEditorExtensionElement { @@ -18,11 +18,11 @@ export class UmbPropertyEditorUINumberElement extends UmbLitElement implements U @state() private _step?: number; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this._min = config.getValueByAlias('min'); - this._max = config.getValueByAlias('max'); - this._step = config.getValueByAlias('step'); + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + this._min = config?.getValueByAlias('min'); + this._max = config?.getValueByAlias('max'); + this._step = config?.getValueByAlias('step'); } private onInput(e: InputEvent) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/order-direction/property-editor-ui-order-direction.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/order-direction/property-editor-ui-order-direction.element.ts index 14b48efe00..1f3d7beab9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/order-direction/property-editor-ui-order-direction.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/order-direction/property-editor-ui-order-direction.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-order-direction @@ -15,8 +15,8 @@ export class UmbPropertyEditorUIOrderDirectionElement @property() value = ''; - @property({ type: Array, attribute: false }) - public config = new UmbDataTypePropertyCollection(); + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; render() { return html`
umb-property-editor-ui-order-direction
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/overlay-size/property-editor-ui-overlay-size.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/overlay-size/property-editor-ui-overlay-size.element.ts index 13827e4df8..99196b02d7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/overlay-size/property-editor-ui-overlay-size.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/overlay-size/property-editor-ui-overlay-size.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-overlay-size @@ -12,8 +12,8 @@ export class UmbPropertyEditorUIOverlaySizeElement extends UmbLitElement impleme @property() value = ''; - @property({ type: Array, attribute: false }) - public config = new UmbDataTypePropertyCollection(); + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; render() { return html`
umb-property-editor-ui-overlay-size
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/radio-button-list/property-editor-ui-radio-button-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/radio-button-list/property-editor-ui-radio-button-list.element.ts index 9593078431..c8240a518e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/radio-button-list/property-editor-ui-radio-button-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/radio-button-list/property-editor-ui-radio-button-list.element.ts @@ -1,7 +1,7 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import '../../../components/input-radio-button-list/input-radio-button-list.element.js'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; import type { UmbInputRadioButtonListElement } from '../../../components/input-radio-button-list/input-radio-button-list.element.js'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -23,8 +23,9 @@ export class UmbPropertyEditorUIRadioButtonListElement this.#value = value?.trim() || ''; } - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + if (!config) return; const listData: Record | undefined = config.getValueByAlias('items'); if (!listData) return; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/slider/property-editor-ui-slider.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/slider/property-editor-ui-slider.element.ts index a34a51bd32..d3de6e352f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/slider/property-editor-ui-slider.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/slider/property-editor-ui-slider.element.ts @@ -3,14 +3,14 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/ex import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-slider */ @customElement('umb-property-editor-ui-slider') export class UmbPropertyEditorUISliderElement extends UmbLitElement implements UmbPropertyEditorExtensionElement { - @property() + @property({ type: Object }) value: | { to?: number; @@ -36,14 +36,14 @@ export class UmbPropertyEditorUISliderElement extends UmbLitElement implements U @state() _max?: number; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this._enableRange = config.getValueByAlias('enableRange'); - this._initVal1 = config.getValueByAlias('initVal1'); - this._initVal2 = config.getValueByAlias('initVal2'); - this._step = config.getValueByAlias('step'); - this._min = config.getValueByAlias('minVal'); - this._max = config.getValueByAlias('maxVal'); + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + this._enableRange = config?.getValueByAlias('enableRange'); + this._initVal1 = config?.getValueByAlias('initVal1'); + this._initVal2 = config?.getValueByAlias('initVal2'); + this._step = config?.getValueByAlias('step'); + this._min = config?.getValueByAlias('minVal'); + this._max = config?.getValueByAlias('maxVal'); } #getValueObject(val: string) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/text-box/property-editor-ui-text-box.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/text-box/property-editor-ui-text-box.element.ts index 16fd0cd44f..66c0fe216b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/text-box/property-editor-ui-text-box.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/text-box/property-editor-ui-text-box.element.ts @@ -2,7 +2,7 @@ import { css, html, customElement, property, state, ifDefined } from '@umbraco-c import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; @customElement('umb-property-editor-ui-text-box') export class UmbPropertyEditorUITextBoxElement extends UmbLitElement implements UmbPropertyEditorExtensionElement { @@ -17,10 +17,10 @@ export class UmbPropertyEditorUITextBoxElement extends UmbLitElement implements @state() private _maxChars?: number; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this._type = config.getValueByAlias('inputType') ?? this.#defaultType; - this._maxChars = config.getValueByAlias('maxChars'); + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + this._type = config?.getValueByAlias('inputType') ?? this.#defaultType; + this._maxChars = config?.getValueByAlias('maxChars'); } private onInput(e: InputEvent) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/textarea/property-editor-ui-textarea.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/textarea/property-editor-ui-textarea.element.ts index fc0641e57f..93a93a207a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/textarea/property-editor-ui-textarea.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/textarea/property-editor-ui-textarea.element.ts @@ -2,7 +2,7 @@ import { css, html, customElement, property, state, ifDefined, styleMap } from ' import { UUITextStyles, UUITextareaElement } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; @customElement('umb-property-editor-ui-textarea') export class UmbPropertyEditorUITextareaElement extends UmbLitElement implements UmbPropertyEditorExtensionElement { @@ -24,12 +24,12 @@ export class UmbPropertyEditorUITextareaElement extends UmbLitElement implements @state() private _css?: any; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this._maxChars = config.getValueByAlias('maxChars'); - this._rows = config.getValueByAlias('rows'); - this._minHeight = config.getValueByAlias('minHeight'); - this._maxHeight = config.getValueByAlias('maxHeight'); + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + this._maxChars = config?.getValueByAlias('maxChars'); + this._rows = config?.getValueByAlias('rows'); + this._minHeight = config?.getValueByAlias('minHeight'); + this._maxHeight = config?.getValueByAlias('maxHeight'); this._css = { '--uui-textarea-min-height': `${this._minHeight ? `${this._minHeight}px` : 'reset'}`, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/config/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/config/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts index cf5efd2457..e7e37b9f99 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/config/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/config/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts @@ -3,7 +3,7 @@ import { customElement, css, html, property, map, state, PropertyValueMap } from import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbPropertyEditorExtensionElement, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; import { tinymce } from '@umbraco-cms/backoffice/external/tinymce'; const tinyIconSet = tinymce.default?.IconManager.get('default'); @@ -28,7 +28,7 @@ export class UmbPropertyEditorUITinyMceToolbarConfigurationElement if (!value) return; if (typeof value === 'string') { - this.#selectedValues = value.split(',').filter(x => x.length > 0); + this.#selectedValues = value.split(',').filter((x) => x.length > 0); } else if (Array.isArray(value)) { this.#selectedValues = value; } else { @@ -51,8 +51,8 @@ export class UmbPropertyEditorUITinyMceToolbarConfigurationElement return this.#selectedValues; } - @property({ type: Array, attribute: false }) - config?: UmbDataTypePropertyCollection; + @property({ attribute: false }) + config?: UmbDataTypeConfigCollection; @state() private _toolbarConfig: ToolbarConfig[] = []; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts index 4222d3be56..d1f410ed90 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts @@ -2,21 +2,20 @@ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-tiny-mce */ @customElement('umb-property-editor-ui-tiny-mce') export class UmbPropertyEditorUITinyMceElement extends UmbLitElement implements UmbPropertyEditorExtensionElement { - - #configuration?: UmbDataTypePropertyCollection; + #configuration?: UmbDataTypeConfigCollection; @property({ type: String }) value = ''; @property({ attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { + public set config(config: UmbDataTypeConfigCollection | undefined) { this.#configuration = config; } @@ -41,4 +40,4 @@ declare global { interface HTMLElementTagNameMap { 'umb-property-editor-ui-tiny-mce': UmbPropertyEditorUITinyMceElement; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/property-editor-ui-tiny-mce.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/property-editor-ui-tiny-mce.stories.ts index cc623647e9..c099b3d2b5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/property-editor-ui-tiny-mce.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tiny-mce/property-editor-ui-tiny-mce.stories.ts @@ -1,10 +1,10 @@ import type { Meta, StoryObj } from '@storybook/web-components'; import type { UmbPropertyEditorUITinyMceElement } from './property-editor-ui-tiny-mce.element.js'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; import './property-editor-ui-tiny-mce.element.js'; -const config = new UmbDataTypePropertyCollection([ +const config = new UmbDataTypeConfigCollection([ { alias: 'hideLabel', value: true, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/toggle/property-editor-ui-toggle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/toggle/property-editor-ui-toggle.element.ts index aec736e0b3..2460734f2f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/toggle/property-editor-ui-toggle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/toggle/property-editor-ui-toggle.element.ts @@ -1,7 +1,7 @@ import { UmbInputToggleElement } from '../../../components/input-toggle/input-toggle.element.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -10,7 +10,7 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; */ @customElement('umb-property-editor-ui-toggle') export class UmbPropertyEditorUIToggleElement extends UmbLitElement implements UmbPropertyEditorExtensionElement { - @property() + @property({ type: Boolean }) value: undefined | boolean = undefined; @state() @@ -22,12 +22,12 @@ export class UmbPropertyEditorUIToggleElement extends UmbLitElement implements U @state() _showLabels?: boolean; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this.value ??= config.getValueByAlias('default') ?? false; - this._labelOff = config.getValueByAlias('labelOff'); - this._labelOn = config.getValueByAlias('labelOn'); - this._showLabels = config.getValueByAlias('showLabels'); + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + this.value ??= config?.getValueByAlias('default') ?? false; + this._labelOff = config?.getValueByAlias('labelOff'); + this._labelOn = config?.getValueByAlias('labelOn'); + this._showLabels = config?.getValueByAlias('showLabels'); } private _onChange(event: CustomEvent) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tree-picker/property-editor-ui-tree-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tree-picker/property-editor-ui-tree-picker.element.ts index a974d03090..8011064c20 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tree-picker/property-editor-ui-tree-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/tree-picker/property-editor-ui-tree-picker.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-tree-picker @@ -12,8 +12,8 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen @property() value = ''; - @property({ type: Array, attribute: false }) - public config = new UmbDataTypePropertyCollection(); + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; render() { return html`
umb-property-editor-ui-tree-picker
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/upload-field/property-editor-ui-upload-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/upload-field/property-editor-ui-upload-field.element.ts index a045e2150f..aef35cec95 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/upload-field/property-editor-ui-upload-field.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/upload-field/property-editor-ui-upload-field.element.ts @@ -3,7 +3,7 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/ex import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import type { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-upload-field @@ -13,10 +13,10 @@ export class UmbPropertyEditorUIUploadFieldElement extends UmbLitElement impleme @property() value = ''; - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this._fileExtensions = config.getValueByAlias('fileExtensions'); - this._multiple = config.getValueByAlias('multiple'); + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + this._fileExtensions = config?.getValueByAlias('fileExtensions'); + this._multiple = config?.getValueByAlias('multiple'); } @state() diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/user-picker/property-editor-ui-user-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/user-picker/property-editor-ui-user-picker.element.ts index f31d862474..1e1441f742 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/user-picker/property-editor-ui-user-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/user-picker/property-editor-ui-user-picker.element.ts @@ -2,7 +2,7 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-user-picker @@ -12,8 +12,8 @@ export class UmbPropertyEditorUIUserPickerElement extends UmbLitElement implemen @property() value = ''; - @property({ type: Array, attribute: false }) - public config = new UmbDataTypePropertyCollection(); + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; // TODO: implement config render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/value-type/property-editor-ui-value-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/value-type/property-editor-ui-value-type.element.ts index c6bdca39f8..4b0cf8802c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/value-type/property-editor-ui-value-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editors/uis/value-type/property-editor-ui-value-type.element.ts @@ -3,7 +3,7 @@ import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-value-type @@ -38,8 +38,8 @@ export class UmbPropertyEditorUIValueTypeElement extends UmbLitElement implement { name: 'Long String', value: 'TEXT' }, ]; - @property({ type: Array, attribute: false }) - public config?: UmbDataTypePropertyCollection; + @property({ attribute: false }) + public config?: UmbDataTypeConfigCollection; #onChange(e: UUISelectEvent) { this.value = e.target.value as string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index c5506c2d05..1968292fcb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -1,4 +1,4 @@ -import { UmbControllerInterface, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; const autoScrollSensitivity = 50; const autoScrollSpeed = 16; @@ -114,7 +114,7 @@ export type UmbSorterConfig = Omit< * @implements {UmbControllerInterface} * @description This controller can make user able to sort items. */ -export class UmbSorterController implements UmbControllerInterface { +export class UmbSorterController implements UmbController { #host; #config: INTERNAL_UmbSorterConfig; #observer; @@ -137,7 +137,7 @@ export class UmbSorterController implements UmbControllerInterface { private _lastIndicationContainerVM: UmbSorterController | null = null; - public get unique() { + public get controllerAlias() { return this.#config.identifier; } @@ -427,7 +427,7 @@ export class UmbSorterController implements UmbControllerInterface { : null; if (parentContainer) { const parentContainerVM = (parentContainer as any)['__umbBlockGridSorterController'](); - if (parentContainerVM.unique === this.unique) { + if (parentContainerVM.unique === this.controllerAlias) { this.#currentContainerElement = parentContainer as Element; this.#currentContainerVM = parentContainerVM; if (this.#config.onContainerChange) { @@ -516,7 +516,7 @@ export class UmbSorterController implements UmbControllerInterface { const subOffsetEdge = subContainerHasItems ? -10 : 20; if (isWithinRect(this.#dragX, this.#dragY, subBoundaryRect, subOffsetEdge)) { const subVm = (subLayoutEl as any)['__umbBlockGridSorterController'](); - if (subVm.unique === this.unique) { + if (subVm.unique === this.controllerAlias) { this.#currentContainerElement = subLayoutEl as HTMLElement; this.#currentContainerVM = subVm; if (this.#config.onContainerChange) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/store/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/store/index.ts index 843f8f5513..0ab513ac78 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/store/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/store/index.ts @@ -2,7 +2,6 @@ export * from './entity-tree-store.js'; export * from './file-system-tree.store.js'; export * from './item-store.interface.js'; export * from './store-base.js'; -export * from './store-extension-initializer.js'; export * from './store.interface.js'; export * from './store.js'; export * from './tree-store.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts index 335882f808..2c8bb79a27 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts @@ -1,16 +1,16 @@ import { UmbStore } from './store.interface.js'; import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; // TODO: Make a Store interface? export class UmbStoreBase implements UmbStore { - protected _host: UmbControllerHostElement; + protected _host: UmbControllerHost; protected _data: UmbArrayState; public readonly storeAlias: string; - constructor(_host: UmbControllerHostElement, storeAlias: string, data: UmbArrayState) { + constructor(_host: UmbControllerHost, storeAlias: string, data: UmbArrayState) { this._host = _host; this.storeAlias = storeAlias; this._data = data; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/store/store-extension-initializer.ts b/src/Umbraco.Web.UI.Client/src/packages/core/store/store-extension-initializer.ts deleted file mode 100644 index 36c907a54c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/store/store-extension-initializer.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { createExtensionClass } from '@umbraco-cms/backoffice/extension-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; - -export class UmbStoreExtensionInitializer { - public host: UmbControllerHostElement; - #storeMap = new Map(); - - constructor(host: UmbControllerHostElement) { - this.host = host; - - new UmbObserverController( - this.host, - umbExtensionsRegistry.extensionsOfTypes(['store', 'treeStore', 'itemStore']), - (stores) => { - if (!stores) return; - - stores.forEach((store) => { - if (this.#storeMap.has(store.alias)) return; - - // Instantiate and provide stores. Stores are self providing when the class is instantiated. - this.#storeMap.set(store.alias, createExtensionClass(store, [this.host])); - }); - } - ); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/themes/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/themes/index.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/settings/themes/index.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/themes/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/themes/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts similarity index 64% rename from src/Umbraco.Web.UI.Client/src/packages/settings/themes/manifests.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts index c77d0f82fc..ca2c0d1285 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/themes/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts @@ -1,6 +1,12 @@ -import type { ManifestTheme } from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; -export const themes: Array = [ +export const themes: Array = [ + { + type: 'globalContext', + alias: 'Umb.GlobalContext.Theme', + name: 'Theme Context', + loader: () => import('./theme.context.js'), + }, { type: 'theme', alias: 'umb-light-theme', diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/themes/theme.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/themes/theme.context.ts similarity index 72% rename from src/Umbraco.Web.UI.Client/src/packages/settings/themes/theme.context.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/themes/theme.context.ts index dcb40d9629..53b5efb947 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/themes/theme.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/themes/theme.context.ts @@ -1,26 +1,23 @@ -import { manifests } from './manifests.js'; import { map } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbContextProviderController, UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import { UmbStringState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBaseController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { ManifestTheme, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; const LOCAL_STORAGE_KEY = 'umb-theme-alias'; -export class UmbThemeContext { - private _host: UmbControllerHostElement; - +export class UmbThemeContext extends UmbBaseController { #theme = new UmbStringState('umb-light-theme'); - public readonly theme = this.#theme.asObservable(); + #themeObserver?: UmbObserverController; - private themeSubscription?: UmbObserverController; + public readonly theme = this.#theme.asObservable(); #styleElement: HTMLLinkElement | HTMLStyleElement | null = null; constructor(host: UmbControllerHostElement) { - this._host = host; + super(host); - new UmbContextProviderController(host, UMB_THEME_CONTEXT_TOKEN, this); + this.provideContext(UMB_THEME_CONTEXT_TOKEN, this); const storedTheme = localStorage.getItem(LOCAL_STORAGE_KEY); if (storedTheme) { @@ -31,11 +28,10 @@ export class UmbThemeContext { public setThemeByAlias(themeAlias: string) { this.#theme.next(themeAlias); - this.themeSubscription?.destroy(); + this.#themeObserver?.destroy(); if (themeAlias) { localStorage.setItem(LOCAL_STORAGE_KEY, themeAlias); - this.themeSubscription = new UmbObserverController( - this._host, + this.#themeObserver = this.observe( umbExtensionsRegistry .extensionsOfType('theme') .pipe(map((extensions) => extensions.filter((extension) => extension.alias === themeAlias))), @@ -73,11 +69,5 @@ export class UmbThemeContext { export const UMB_THEME_CONTEXT_TOKEN = new UmbContextToken('umbThemeContext'); -// TODO: Can we do this in a smarter way: -const registerExtensions = (manifests: Array) => { - manifests.forEach((manifest) => { - umbExtensionsRegistry.register(manifest); - }); -}; - -registerExtensions([...manifests]); +// Default export to enable this as a globalContext extension js: +export default UmbThemeContext; diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/themes/themes/dark.theme.css b/src/Umbraco.Web.UI.Client/src/packages/core/themes/themes/dark.theme.css similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/settings/themes/themes/dark.theme.css rename to src/Umbraco.Web.UI.Client/src/packages/core/themes/themes/dark.theme.css diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/themes/themes/high-contrast.theme.css b/src/Umbraco.Web.UI.Client/src/packages/core/themes/themes/high-contrast.theme.css similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/settings/themes/themes/high-contrast.theme.css rename to src/Umbraco.Web.UI.Client/src/packages/core/themes/themes/high-contrast.theme.css diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts index 5002f85c41..815cd1d94f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts @@ -1,15 +1,15 @@ import { Observable, map } from '@umbraco-cms/backoffice/external/rxjs'; import { UmbPagedData, UmbTreeRepository } from '@umbraco-cms/backoffice/repository'; import { ManifestTree, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { UmbBooleanState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbBaseController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { createExtensionClass } from '@umbraco-cms/backoffice/extension-api'; import { ProblemDetailsModel, TreeItemPresentationModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils'; // TODO: update interface -export interface UmbTreeContext { +export interface UmbTreeContext extends UmbBaseController { readonly selectable: Observable; readonly selection: Observable>; setSelectable(value: boolean): void; @@ -28,10 +28,9 @@ export interface UmbTreeContext } export class UmbTreeContextBase + extends UmbBaseController implements UmbTreeContext { - public host: UmbControllerHostElement; - #selectionManager = new UmbSelectionManagerBase(); #selectable = new UmbBooleanState(false); @@ -53,8 +52,8 @@ export class UmbTreeContextBase }); constructor(host: UmbControllerHostElement) { - this.host = host; - new UmbContextProviderController(host, 'umbTreeContext', this); + super(host); + this.provideContext('umbTreeContext', this); } // TODO: find a generic way to do this @@ -105,12 +104,12 @@ export class UmbTreeContextBase public select(unique: string | null) { if (!this.getSelectable()) return; this.#selectionManager.select(unique); - this.host.dispatchEvent(new CustomEvent('selected')); + this._host.getHostElement().dispatchEvent(new CustomEvent('selected')); } public deselect(unique: string | null) { this.#selectionManager.deselect(unique); - this.host.dispatchEvent(new CustomEvent('selected')); + this._host.getHostElement().dispatchEvent(new CustomEvent('selected')); } public async requestTreeRoot() { @@ -140,8 +139,7 @@ export class UmbTreeContextBase } #observeTreeManifest() { - new UmbObserverController( - this.host, + this.observe( umbExtensionsRegistry .extensionsOfType('tree') .pipe(map((treeManifests) => treeManifests.find((treeManifest) => treeManifest.alias === this.#treeAlias))), @@ -157,14 +155,13 @@ export class UmbTreeContextBase const repositoryAlias = treeManifest.meta.repositoryAlias; if (!repositoryAlias) throw new Error('Tree must have a repository alias.'); - new UmbObserverController( - this.host, + this.observe( umbExtensionsRegistry.getByTypeAndAlias('repository', treeManifest.meta.repositoryAlias), async (repositoryManifest) => { if (!repositoryManifest) return; try { - const result = await createExtensionClass>(repositoryManifest, [this.host]); + const result = await createExtensionClass>(repositoryManifest, [this._host]); this.repository = result; this.#checkIfInitialized(); } catch (error) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/core/umbraco-package.ts index 0d8c30b4a5..5a4b160bb8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/umbraco-package.ts @@ -1,6 +1,8 @@ +import { ManifestTypes } from './extension-registry/index.js'; + export const name = 'Umbraco.Core'; export const version = '0.0.1'; -export const extensions = [ +export const extensions: Array = [ { name: 'Core Entry Point', alias: 'Umb.EntryPoint.Core', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types/index.ts new file mode 100644 index 0000000000..8ae28a46ca --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types/index.ts @@ -0,0 +1 @@ +export * from './workspace-property-data.type'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types/workspace-property-data.type.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types/workspace-property-data.type.ts new file mode 100644 index 0000000000..b9e1788774 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types/workspace-property-data.type.ts @@ -0,0 +1,9 @@ +import { type UmbDataTypeConfig } from '../../property-editors/index.js'; + +export type WorkspacePropertyData = { + alias?: string; + label?: string; + description?: string; + value?: ValueType | null; + config?: UmbDataTypeConfig; // This could potentially then come from hardcoded JS object and not the DataType store. +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.context.ts index c1e2a7f790..871eb31e98 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.context.ts @@ -1,45 +1,40 @@ import { UmbWorkspaceVariableEntityContextInterface } from '../workspace-context/workspace-variable-entity-context.interface.js'; import { UmbPropertyEditorExtensionElement } from '../../extension-registry/interfaces/property-editor-ui-extension-element.interface.js'; -import { BehaviorSubject } from '@umbraco-cms/backoffice/external/rxjs'; +import { type WorkspacePropertyData } from '../types/workspace-property-data.type.js'; import { UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN, UMB_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { DataTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbClassState, UmbObjectState, UmbStringState, UmbObserverController, + UmbBasicState, } from '@umbraco-cms/backoffice/observable-api'; import { UmbContextConsumerController, UmbContextProviderController, UmbContextToken, } from '@umbraco-cms/backoffice/context-api'; - -// If we get this from the server then we can consider using TypeScripts Partial<> around the model from the Management-API. -export type WorkspacePropertyData = { - alias?: string; - label?: string; - description?: string; - value?: ValueType | null; - config?: DataTypeResponseModel['values']; // This could potentially then come from hardcoded JS object and not the DataType store. -}; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; export class UmbWorkspacePropertyContext { #host: UmbControllerHostElement; private _providerController: UmbContextProviderController; - private _data = new UmbObjectState>({}); + #data = new UmbObjectState>({}); - public readonly alias = this._data.getObservablePart((data) => data.alias); - public readonly label = this._data.getObservablePart((data) => data.label); - public readonly description = this._data.getObservablePart((data) => data.description); - public readonly value = this._data.getObservablePart((data) => data.value); - public readonly config = this._data.getObservablePart((data) => data.config); + public readonly alias = this.#data.getObservablePart((data) => data.alias); + public readonly label = this.#data.getObservablePart((data) => data.label); + public readonly description = this.#data.getObservablePart((data) => data.description); + public readonly value = this.#data.getObservablePart((data) => data.value); + public readonly configValues = this.#data.getObservablePart((data) => data.config); - private _editor = new BehaviorSubject(undefined); + #configCollection = new UmbClassState(undefined); + public readonly config = this.#configCollection.asObservable(); + + private _editor = new UmbBasicState(undefined); public readonly editor = this._editor.asObservable(); setEditor(editor: UmbPropertyEditorExtensionElement | undefined) { this._editor.next(editor ?? undefined); @@ -67,6 +62,10 @@ export class UmbWorkspacePropertyContext { this._providerController = new UmbContextProviderController(host, UMB_WORKSPACE_PROPERTY_CONTEXT_TOKEN, this); + this.configValues.subscribe((configValues) => { + this.#configCollection.next(configValues ? new UmbDataTypeConfigCollection(configValues) : undefined); + }); + this.variantId.subscribe((propertyVariantId) => { if (propertyVariantId) { if (!this._workspaceVariantConsumer) { @@ -94,28 +93,28 @@ export class UmbWorkspacePropertyContext { } public setAlias(alias: WorkspacePropertyData['alias']) { - this._data.update({ alias }); + this.#data.update({ alias }); } public setLabel(label: WorkspacePropertyData['label']) { - this._data.update({ label }); + this.#data.update({ label }); } public setDescription(description: WorkspacePropertyData['description']) { - this._data.update({ description }); + this.#data.update({ description }); } public setValue(value: WorkspacePropertyData['value']) { // Note: Do not try to compare new / old value, as it can of any type. We trust the UmbObjectState in doing such. - this._data.update({ value }); + this.#data.update({ value }); } public changeValue(value: WorkspacePropertyData['value']) { this.setValue(value); - const alias = this._data.getValue().alias; + const alias = this.#data.getValue().alias; if (alias) { this._workspaceContext?.setPropertyValue(alias, value, this.#variantId.getValue()); } } public setConfig(config: WorkspacePropertyData['config'] | undefined) { - this._data.update({ config }); + this.#data.update({ config }); } public setVariantId(variantId: UmbVariantId | undefined) { this.#variantId.next(variantId); @@ -129,7 +128,7 @@ export class UmbWorkspacePropertyContext { } public destroy(): void { - this._data.unsubscribe(); + this.#data.unsubscribe(); this._providerController.destroy(); // This would also be handled by the controller host, but if someone wanted to replace/remove this context without the host being destroyed. Then we have clean up out selfs here. } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.element.ts index 7b809abfb5..b03cfdbd5f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.element.ts @@ -1,3 +1,4 @@ +import { type UmbDataTypeConfig } from '../../property-editors/index.js'; import { UmbWorkspacePropertyContext } from './workspace-property.context.js'; import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { css, html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; @@ -6,8 +7,7 @@ import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api'; import { ManifestPropertyEditorUi, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { DataTypePropertyPresentationModel } from '@umbraco-cms/backoffice/backend-api'; -import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; /** * @element umb-workspace-property @@ -85,8 +85,8 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { * @attr * @default '' */ - @property({ type: Object, attribute: false }) - public set config(value: DataTypePropertyPresentationModel[] | undefined) { + @property({ type: Array, attribute: false }) + public set config(value: UmbDataTypeConfig | undefined) { this._propertyContext.setConfig(value); } @@ -123,10 +123,8 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { private _propertyContext = new UmbWorkspacePropertyContext(this); - private propertyEditorUIObserver?: UmbObserverController; - private _valueObserver?: UmbObserverController; - private _configObserver?: UmbObserverController; + private _configObserver?: UmbObserverController; constructor() { super(); @@ -154,8 +152,7 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { }; private _observePropertyEditorUI() { - this.propertyEditorUIObserver?.destroy(); - this.propertyEditorUIObserver = this.observe( + this.observe( umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUi', this._propertyEditorUiAlias), (manifest) => { this._gotEditorUI(manifest); @@ -202,7 +199,7 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { this._propertyContext.config, (config) => { if (this._element && config) { - this._element.config = new UmbDataTypePropertyCollection(config); + this._element.config = config; } }, '_observePropertyConfig' diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-editor.element.ts index d3c7f3b47d..42cfc19a43 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-editor.element.ts @@ -57,7 +57,7 @@ export class UmbDocumentTypeWorkspaceEditorElement extends UmbLitElement { // TODO: Would be good with a more general way to bring focus to the name input. (this.shadowRoot?.querySelector('#name') as HTMLElement)?.focus(); } - this.removeControllerByUnique('_observeIsNew'); + this.removeControllerByAlias('_observeIsNew'); }, '_observeIsNew' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.element.ts index b7ec6fe7c3..6846cc0fe1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.element.ts @@ -40,7 +40,7 @@ export class UmbDocumentTypeWorkspaceElement extends UmbLitElement { path: 'edit/:id', component: import('./document-type-workspace-editor.element.js'), setup: (_component, info) => { - this.removeControllerByUnique('_observeIsNew'); + this.removeControllerByAlias('_observeIsNew'); const id = info.match.params.id; this.#workspaceContext.load(id); }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts index 3c810524f2..22890ad896 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts @@ -2,7 +2,7 @@ import type { UmbInputDocumentElement } from '../../components/input-document/in import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; @customElement('umb-property-editor-ui-document-picker') export class UmbPropertyEditorUIContentPickerElement @@ -19,9 +19,9 @@ export class UmbPropertyEditorUIContentPickerElement this._value = value || []; } - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - const validationLimit = config.find((x) => x.alias === 'validationLimit'); + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + const validationLimit = config?.find((x) => x.alias === 'validationLimit'); this._limitMin = (validationLimit?.value as any).min; this._limitMax = (validationLimit?.value as any).max; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/package-entry-point.ts b/src/Umbraco.Web.UI.Client/src/packages/media/manifests.ts similarity index 66% rename from src/Umbraco.Web.UI.Client/src/packages/media/package-entry-point.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/manifests.ts index 440238766c..138fdd7f88 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/package-entry-point.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/manifests.ts @@ -2,10 +2,5 @@ import { manifests as mediaSectionManifests } from './section.manifests.js'; import { manifests as mediaMenuManifests } from './menu.manifests.js'; import { manifests as mediaManifests } from './media/manifests.js'; import { manifests as mediaTypesManifests } from './media-types/manifests.js'; -import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; export const manifests = [...mediaSectionManifests, ...mediaMenuManifests, ...mediaManifests, ...mediaTypesManifests]; - -export const onInit: UmbEntryPointOnInit = (_host, extensionRegistry) => { - extensionRegistry.registerMany(manifests); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/media.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/media.repository.ts index db3a6f8f12..64730ae059 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/media.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/media.repository.ts @@ -14,8 +14,6 @@ import { import { UmbDetailRepository } from '@umbraco-cms/backoffice/repository'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; -type ItemDetailType = MediaDetails; - export class UmbMediaRepository implements UmbTreeRepository, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/media/umbraco-package.ts index 94288bede8..33cc7c2bc3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/umbraco-package.ts @@ -1,9 +1,9 @@ export const name = 'Umbraco.Core.MediaManagement'; export const extensions = [ { - name: 'Media Management Entry Point', - alias: 'Umb.EntryPoint.MediaManagement', - type: 'entryPoint', - loader: () => import('./package-entry-point.js'), + name: 'Media Management Bundle', + alias: 'Umb.Bundle.MediaManagement', + type: 'bundle', + loader: () => import('./manifests.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/package-entry-point.ts b/src/Umbraco.Web.UI.Client/src/packages/members/manifests.ts similarity index 72% rename from src/Umbraco.Web.UI.Client/src/packages/members/package-entry-point.ts rename to src/Umbraco.Web.UI.Client/src/packages/members/manifests.ts index eab135eef0..f928869b69 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/package-entry-point.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/manifests.ts @@ -3,7 +3,6 @@ import { manifests as menuSectionManifests } from './menu.manifests.js'; import { manifests as memberGroupManifests } from './member-groups/manifests.js'; import { manifests as memberTypeManifests } from './member-types/manifests.js'; import { manifests as memberManifests } from './members/manifests.js'; -import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; export const manifests = [ ...memberSectionManifests, @@ -12,7 +11,3 @@ export const manifests = [ ...memberTypeManifests, ...memberManifests, ]; - -export const onInit: UmbEntryPointOnInit = (_host, extensionRegistry) => { - extensionRegistry.registerMany(manifests); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/members/umbraco-package.ts index c3376f3876..d9f3f5ca2d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/umbraco-package.ts @@ -1,9 +1,9 @@ export const name = 'Umbraco.Core.MemberManagement'; export const extensions = [ { - name: 'Member Management Entry Point', - alias: 'Umb.EntryPoint.MemberManagement', - type: 'entryPoint', - loader: () => import('./package-entry-point.js'), + name: 'Member Management Bundle', + alias: 'Umb.Bundle.MemberManagement', + type: 'bundle', + loader: () => import('./manifests.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/packages/package-entry-point.ts b/src/Umbraco.Web.UI.Client/src/packages/packages/manifests.ts similarity index 70% rename from src/Umbraco.Web.UI.Client/src/packages/packages/package-entry-point.ts rename to src/Umbraco.Web.UI.Client/src/packages/packages/manifests.ts index 01a54b2026..764eb0bdb4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/packages/package-entry-point.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/packages/manifests.ts @@ -2,7 +2,6 @@ import { manifests as repositoryManifests } from './package/repository/manifests import { manifests as packageBuilderManifests } from './package-builder/manifests.js'; import { manifests as packageRepoManifests } from './package-repo/manifests.js'; import { manifests as packageSectionManifests } from './package-section/manifests.js'; -import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; export const manifests = [ ...repositoryManifests, @@ -10,7 +9,3 @@ export const manifests = [ ...packageRepoManifests, ...packageSectionManifests, ]; - -export const onInit: UmbEntryPointOnInit = (_host, extensionRegistry) => { - extensionRegistry.registerMany(manifests); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/packages/package/repository/server-extension.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/packages/package/repository/server-extension.controller.ts index 2a2d96da1e..00619bf058 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/packages/package/repository/server-extension.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/packages/package/repository/server-extension.controller.ts @@ -1,9 +1,9 @@ import { UmbPackageRepository } from './package.repository.js'; import { Subject, takeUntil } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBaseController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry'; -export class UmbExtensionInitializer extends UmbController { +export class UmbExtensionInitializer extends UmbBaseController { #host: UmbControllerHostElement; #extensionRegistry: UmbBackofficeExtensionRegistry; #unobserve = new Subject(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/packages/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/packages/umbraco-package.ts index 156094f0c2..860930db14 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/packages/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/packages/umbraco-package.ts @@ -1,9 +1,9 @@ export const name = 'Umbraco.Core.PackageManagement'; export const extensions = [ { - name: 'Package Management Entry Point', - alias: 'Umb.EntryPoint.PackageManagement', - type: 'entryPoint', - loader: () => import('./package-entry-point.js'), + name: 'Package Management Bundle', + alias: 'Umb.Bundle.PackageManagement', + type: 'bundle', + loader: () => import('./manifests.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/package-entry-point.ts b/src/Umbraco.Web.UI.Client/src/packages/search/package-entry-point.ts deleted file mode 100644 index 2e31400609..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/search/package-entry-point.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { manifests as searchManifests } from './manifests.js'; - -import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; - -export const manifests = [...searchManifests]; - -export const onInit: UmbEntryPointOnInit = (_host, extensionRegistry) => { - extensionRegistry.registerMany(manifests); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/search/umbraco-package.ts index a58633ae59..19cfeaa65d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/umbraco-package.ts @@ -1,9 +1,9 @@ export const name = 'Umbraco.Core.Search'; export const extensions = [ { - name: 'Search Entry Point', - alias: 'Umb.EntryPoint.Search', - type: 'entryPoint', - loader: () => import('./package-entry-point.js'), + name: 'Search Bundle', + alias: 'Umb.Bundle.Search', + type: 'bundle', + loader: () => import('./manifests.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/ref-data-type/ref-data-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/ref-data-type/ref-data-type.element.ts index 10179c4fef..ac8c47070a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/ref-data-type/ref-data-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/ref-data-type/ref-data-type.element.ts @@ -35,7 +35,7 @@ export class UmbRefDataTypeElement extends UmbElementMixin(UUIRefNodeElement) { 'dataType' ); } else { - this.removeControllerByUnique('dataType'); + this.removeControllerByAlias('dataType'); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace-editor.element.ts index 19e61a1efd..7f40fa887b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace-editor.element.ts @@ -40,7 +40,7 @@ export class UmbDataTypeWorkspaceEditorElement extends UmbLitElement { (this.shadowRoot!.querySelector('#nameInput') as HTMLElement).focus(); }); } - this.removeControllerByUnique('_observeIsNew'); + this.removeControllerByAlias('_observeIsNew'); }, '_observeIsNew' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/views/details/data-type-details-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/views/details/data-type-details-workspace-view.element.ts index d623e06fb9..c12c8b8ac4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/views/details/data-type-details-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/views/details/data-type-details-workspace-view.element.ts @@ -101,12 +101,12 @@ export class UmbDataTypeDetailsWorkspaceViewEditElement private _observePropertyEditorUI(propertyEditorUiAlias?: string) { if (!propertyEditorUiAlias) { this._propertyEditorUiName = this._propertyEditorUiIcon = this._propertyEditorUiAlias = undefined; - this.removeControllerByUnique('_observePropertyEditorUI'); + this.removeControllerByAlias('_observePropertyEditorUI'); return; } // remove the '_observepropertyEditorSchemaForDefaultUI' controller, as we do not want to observe for default value anymore: - this.removeControllerByUnique('_observepropertyEditorSchemaForDefaultUI'); + this.removeControllerByAlias('_observepropertyEditorSchemaForDefaultUI'); this.observe( umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUi', propertyEditorUiAlias), @@ -151,16 +151,18 @@ export class UmbDataTypeDetailsWorkspaceViewEditElement private _renderPropertyEditorUI() { return html` - ${this._propertyEditorUiAlias + ${this._propertyEditorUiAlias && this._propertyEditorSchemaAlias ? html` - + ${this._propertyEditorUiIcon + ? html` ` + : ''} diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/extensions/workspace/extension-root-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/extensions/workspace/extension-root-workspace.element.ts index 488f8e6788..ada92e37b6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/extensions/workspace/extension-root-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/extensions/workspace/extension-root-workspace.element.ts @@ -65,9 +65,9 @@ export class UmbExtensionRootWorkspaceElement extends UmbLitElement { Type - Weight Name Alias + Weight Actions @@ -75,11 +75,11 @@ export class UmbExtensionRootWorkspaceElement extends UmbLitElement { (extension) => html` ${extension.type} - ${extension.weight ? extension.weight : 'Not Set'} ${isManifestElementNameType(extension) ? extension.name : `[Custom extension] ${extension.name}`} ${extension.alias} + ${extension.weight ? extension.weight : ''} ('UmbAppLanguageContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/app-language-select/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/app-language-select/manifests.ts index f2d351b8fa..d11ad574d8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/app-language-select/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/app-language-select/manifests.ts @@ -1,6 +1,12 @@ -import { ManifestSectionSidebarApp } from '@umbraco-cms/backoffice/extension-registry'; +import { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; -const entityActions: Array = [ +const entityActions: Array = [ + { + type: 'globalContext', + alias: 'Umb.GlobalContext.AppLanguage', + name: 'App Language Context', + loader: () => import('./app-language.context.js'), + }, { type: 'sectionSidebarApp', alias: 'Umb.SectionSidebarItem.LanguageSelect', diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.element.ts index 024ff5b6a6..249f4acb69 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.element.ts @@ -32,7 +32,7 @@ export class UmbLanguageWorkspaceElement extends UmbLitElement { path: 'edit/:isoCode', component: this.#getComponentElement, setup: (_component, info) => { - this.removeControllerByUnique('_observeIsNew'); + this.removeControllerByAlias('_observeIsNew'); this.#languageWorkspaceContext.load(info.match.params.isoCode); }, }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/manifests.ts index 3aee37b9e4..6dc8c64b6b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/manifests.ts @@ -1,21 +1,21 @@ -import { manifests as settingsSectionManifests } from './section.manifests.js'; -import { manifests as settingsMenuManifests } from './menu.manifests.js'; +import { manifests as cultureManifests } from './cultures/manifests.js'; import { manifests as dashboardManifests } from './dashboards/manifests.js'; import { manifests as dataTypeManifests } from './data-types/manifests.js'; -import { manifests as relationTypeManifests } from './relation-types/manifests.js'; import { manifests as extensionManifests } from './extensions/manifests.js'; -import { manifests as cultureManifests } from './cultures/manifests.js'; import { manifests as languageManifests } from './languages/manifests.js'; import { manifests as logviewerManifests } from './logviewer/manifests.js'; +import { manifests as relationTypeManifests } from './relation-types/manifests.js'; +import { manifests as settingsMenuManifests } from './menu.manifests.js'; +import { manifests as settingsSectionManifests } from './section.manifests.js'; export const manifests = [ - ...settingsSectionManifests, - ...settingsMenuManifests, + ...cultureManifests, ...dashboardManifests, ...dataTypeManifests, ...extensionManifests, - ...cultureManifests, ...languageManifests, ...logviewerManifests, ...relationTypeManifests, + ...settingsMenuManifests, + ...settingsSectionManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/package-entry-point.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/package-entry-point.ts deleted file mode 100644 index 0391778271..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/package-entry-point.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - UMB_APP_LANGUAGE_CONTEXT_TOKEN, - UmbAppLanguageContext, -} from './languages/app-language-select/app-language.context.js'; -import { UmbThemeContext } from './themes/theme.context.js'; -import { manifests } from './manifests.js'; -import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; -import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; - -export const onInit: UmbEntryPointOnInit = (host, extensionRegistry) => { - extensionRegistry.registerMany(manifests); - new UmbContextProviderController(host, UMB_APP_LANGUAGE_CONTEXT_TOKEN, new UmbAppLanguageContext(host)); - new UmbThemeContext(host); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/umbraco-package.ts index cbf922a5ab..3d01bae2f8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/umbraco-package.ts @@ -1,9 +1,9 @@ export const name = 'Umbraco.Core.Settings'; export const extensions = [ { - name: 'Settings Entry Point', - alias: 'Umb.EntryPoint.Settings', - type: 'entryPoint', - loader: () => import('./package-entry-point.js'), + name: 'Settings Bundle', + alias: 'Umb.Bundle.Settings', + type: 'bundle', + loader: () => import('./manifests.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tags/index.ts b/src/Umbraco.Web.UI.Client/src/packages/tags/index.ts index 37dd11a7dd..3cdf0295e3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tags/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tags/index.ts @@ -1 +1,2 @@ export * from './repository/'; +export * from './components/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tags/package-entry-point.ts b/src/Umbraco.Web.UI.Client/src/packages/tags/manifests.ts similarity index 51% rename from src/Umbraco.Web.UI.Client/src/packages/tags/package-entry-point.ts rename to src/Umbraco.Web.UI.Client/src/packages/tags/manifests.ts index fa1b7ed38d..106d6c4f3c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tags/package-entry-point.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tags/manifests.ts @@ -1,11 +1,4 @@ import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as propertyEditorManifests } from './property-editors/manifests.js'; -import { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; - -import './components/index.js'; export const manifests = [...repositoryManifests, ...propertyEditorManifests]; - -export const onInit: UmbEntryPointOnInit = (host, extensionRegistry) => { - extensionRegistry.registerMany(manifests); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts index 5eea7c8ad8..3b427fc972 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts @@ -2,7 +2,7 @@ import { UmbTagsInputElement } from '../../components/tags-input/tags-input.elem import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; import { UMB_WORKSPACE_PROPERTY_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/workspace'; -import type { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/components'; +import type { UmbDataTypeConfigCollection } from '@umbraco-cms/backoffice/components'; import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -11,8 +11,15 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; */ @customElement('umb-property-editor-ui-tags') export class UmbPropertyEditorUITagsElement extends UmbLitElement implements UmbPropertyEditorExtensionElement { - @property() - value: string[] = []; + private _value: Array = []; + + @property({ type: Array }) + public get value(): Array { + return this._value; + } + public set value(value: Array) { + this._value = value || []; + } @state() private _group?: string; @@ -21,10 +28,10 @@ export class UmbPropertyEditorUITagsElement extends UmbLitElement implements Umb private _culture?: string | null; //TODO: Use type from VariantID - @property({ type: Array, attribute: false }) - public set config(config: UmbDataTypePropertyCollection) { - this._group = config.getValueByAlias('group'); - this.value = config.getValueByAlias('items') ?? []; + @property({ attribute: false }) + public set config(config: UmbDataTypeConfigCollection | undefined) { + this._group = config?.getValueByAlias('group'); + this.value = config?.getValueByAlias('items') ?? []; } constructor() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tags/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/tags/umbraco-package.ts index 584495cc16..98bf4437db 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tags/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tags/umbraco-package.ts @@ -1,10 +1,10 @@ -export const name = 'Umbraco.Core.UserManagement'; +export const name = 'Umbraco.Core.TagManagement'; export const version = '0.0.1'; export const extensions = [ { - name: 'Tags Management Entry Point', - alias: 'Umb.EntryPoint.TagsManagement', - type: 'entryPoint', - loader: () => import('./package-entry-point.js'), + name: 'Tags Management Bundle', + alias: 'Umb.Bundle.TagsManagement', + type: 'bundle', + loader: () => import('./manifests.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/components/index.ts index e4558cca4e..332b9e9f0b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/components/index.ts @@ -1,2 +1,2 @@ -import './file-system-tree-item/file-system-tree-item.element.js'; -import './insert-menu/templating-insert-menu.element.js'; +export * from './file-system-tree-item/file-system-tree-item.element.js'; +export * from './insert-menu/templating-insert-menu.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/index.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/index.ts new file mode 100644 index 0000000000..ab60608518 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/index.ts @@ -0,0 +1,2 @@ +export * from './components/index.js'; +export * from './templates/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/package-entry-point.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/manifests.ts similarity index 65% rename from src/Umbraco.Web.UI.Client/src/packages/templating/package-entry-point.ts rename to src/Umbraco.Web.UI.Client/src/packages/templating/manifests.ts index ee3fc03b34..158a216974 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/package-entry-point.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/manifests.ts @@ -3,10 +3,6 @@ import { manifests as templateManifests } from './templates/manifests.js'; import { manifests as stylesheetManifests } from './stylesheets/manifests.js'; import { manifests as partialManifests } from './partial-views/manifests.js'; import { manifests as modalManifests } from './modals/manifests.js'; -import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; - -import './components/index.js'; -import './templates/index.js'; export const manifests = [ ...menuManifests, @@ -15,7 +11,3 @@ export const manifests = [ ...partialManifests, ...modalManifests, ]; - -export const onInit: UmbEntryPointOnInit = (_host, extensionRegistry) => { - extensionRegistry.registerMany(manifests); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/components/index.ts index 0ede5a73d7..010806f10a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/components/index.ts @@ -1,3 +1,3 @@ -import './template-card/template-card.element.js'; -import './input-template/input-template.element.js'; -import './alias-input/alias-input.js'; +export * from './template-card/template-card.element.js'; +export * from './input-template/input-template.element.js'; +export * from './alias-input/alias-input.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/index.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/index.ts index 610a3732b3..d74d9c71fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/index.ts @@ -1,4 +1,6 @@ -export * from './entities.js'; -export * from './repository/index.js'; import './components/index.js'; import './modals/modal-tokens.js'; + +export * from './entities.js'; +export * from './repository/index.js'; +export * from './components/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/umbraco-package.ts index 204440d83c..f5925d9135 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/umbraco-package.ts @@ -1,9 +1,9 @@ export const name = 'Umbraco.Core.Templating'; export const extensions = [ { - name: 'Templating Entry Point', - alias: 'Umb.EntryPoint.Templating', - type: 'entryPoint', - loader: () => import('./package-entry-point.js'), + name: 'Template Management Bundle', + alias: 'Umb.Bundle.TemplateManagement', + type: 'bundle', + loader: () => import('./manifests.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/translation/index.ts b/src/Umbraco.Web.UI.Client/src/packages/translation/manifests.ts similarity index 54% rename from src/Umbraco.Web.UI.Client/src/packages/translation/index.ts rename to src/Umbraco.Web.UI.Client/src/packages/translation/manifests.ts index 292ffef21b..f8473997dd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/translation/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/translation/manifests.ts @@ -1,10 +1,4 @@ import { manifests as translationSectionManifests } from './section.manifest.js'; import { manifests as dictionaryManifests } from './dictionary/manifests.js'; -import { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; - export const manifests = [...translationSectionManifests, ...dictionaryManifests]; - -export const onInit: UmbEntryPointOnInit = (_host, extensionRegistry) => { - extensionRegistry.registerMany(manifests); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/translation/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/translation/umbraco-package.ts index 7c2053b4b9..65078af2a4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/translation/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/translation/umbraco-package.ts @@ -1,9 +1,9 @@ export const name = 'Umbraco.Core.TranslationManagement'; export const extensions = [ { - name: 'Translation Entry Point', - alias: 'Umb.EntryPoint.Translation', - type: 'entryPoint', - loader: () => import('./index.js'), + name: 'Translation Management Bundle', + alias: 'Umb.Bundle.TranslationManagement', + type: 'bundle', + loader: () => import('./manifests.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/index.ts b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts similarity index 61% rename from src/Umbraco.Web.UI.Client/src/packages/umbraco-news/index.ts rename to src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts index 48b3ef2446..2f24d77a3f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts @@ -1,7 +1,6 @@ -import { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; import { ManifestDashboard } from '@umbraco-cms/backoffice/extension-registry'; -const dashboard: ManifestDashboard = { +export const dashboard: ManifestDashboard = { type: 'dashboard', alias: 'Umb.Dashboard.UmbracoNews', name: 'Umbraco News Dashboard', @@ -15,5 +14,3 @@ const dashboard: ManifestDashboard = { sections: ['Umb.Section.Content'], }, }; - -export const onInit: UmbEntryPointOnInit = (_host, extensionRegistry) => extensionRegistry.register(dashboard); diff --git a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-package.ts index 1331fd50f9..0439c11cd4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-package.ts @@ -1,9 +1,9 @@ export const name = 'Umbraco.Core.UmbracoNews'; export const extensions = [ { - name: 'Umbraco News Entry Point', - alias: 'Umb.EntryPoint.UmbracoNews', - type: 'entryPoint', - loader: () => import('./index.js'), + name: 'Umbraco News Bundle', + alias: 'Umb.Bundle.UmbracoNews', + type: 'bundle', + loader: () => import('./manifests.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/users/current-user/current-user-history.store.ts b/src/Umbraco.Web.UI.Client/src/packages/users/current-user/current-user-history.store.ts index 888278353b..67e38f2f2c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/users/current-user/current-user-history.store.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/users/current-user/current-user-history.store.ts @@ -1,5 +1,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -import { UmbDeepState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/backoffice/store'; export type UmbModelType = 'dialog' | 'sidebar'; @@ -9,13 +11,16 @@ export type UmbCurrentUserHistoryItem = { icon?: string; }; -export class UmbCurrentUserHistoryStore { - #history = new UmbDeepState(>[]); +export class UmbCurrentUserHistoryStore extends UmbStoreBase { + public readonly history = this._data.asObservable(); + public readonly latestHistory = this._data.getObservablePart((historyItems) => historyItems.slice(-10)); - public readonly history = this.#history.asObservable(); - public readonly latestHistory = this.#history.getObservablePart((historyItems) => historyItems.slice(-10)); - - constructor() { + constructor(host: UmbControllerHost) { + super( + host, + UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN.toString(), + new UmbArrayState([]) + ); if (!('navigation' in window)) return; (window as any).navigation.addEventListener('navigate', (event: any) => { const url = new URL(event.destination.url); @@ -31,12 +36,12 @@ export class UmbCurrentUserHistoryStore { * @memberof UmbHistoryService */ public push(historyItem: UmbCurrentUserHistoryItem): void { - const history = this.#history.getValue(); + const history = this._data.getValue(); const lastItem = history[history.length - 1]; // This prevents duplicate entries in the history array. if (!lastItem || lastItem.path !== historyItem.path) { - this.#history.next([...this.#history.getValue(), historyItem]); + this._data.next([...this._data.getValue(), historyItem]); } } @@ -46,10 +51,13 @@ export class UmbCurrentUserHistoryStore { * @memberof UmbHistoryService */ public clear() { - this.#history.next([]); + this._data.next([]); } } export const UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN = new UmbContextToken( 'UmbCurrentUserHistoryStore' ); + +// Default export for the globalContext manifest: +export default UmbCurrentUserHistoryStore; diff --git a/src/Umbraco.Web.UI.Client/src/packages/users/current-user/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/users/current-user/manifests.ts index a0083ea129..f6fe418913 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/users/current-user/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/users/current-user/manifests.ts @@ -3,6 +3,12 @@ import { manifests as userProfileAppsManifests } from './user-profile-apps/manif import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const headerApps: Array = [ + { + type: 'store', + alias: 'Umb.Store.CurrentUser', + name: 'Current User Store', + loader: () => import('./current-user-history.store.js'), + }, { type: 'headerApp', alias: 'Umb.HeaderApp.CurrentUser', diff --git a/src/Umbraco.Web.UI.Client/src/packages/users/current-user/user-profile-apps/user-profile-app-themes.element.ts b/src/Umbraco.Web.UI.Client/src/packages/users/current-user/user-profile-apps/user-profile-app-themes.element.ts index 4ad89cd86e..71f02a56b4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/users/current-user/user-profile-apps/user-profile-app-themes.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/users/current-user/user-profile-apps/user-profile-app-themes.element.ts @@ -1,4 +1,4 @@ -import { UmbThemeContext, UMB_THEME_CONTEXT_TOKEN } from '../../../settings/themes/theme.context.js'; +import { UmbThemeContext, UMB_THEME_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/themes'; import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UUITextStyles, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/users/index.ts b/src/Umbraco.Web.UI.Client/src/packages/users/index.ts index eb363b6357..8fb22d2dd6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/users/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/users/index.ts @@ -1,30 +1,2 @@ -import { manifests as userGroupManifests } from './user-groups/manifests.js'; -import { manifests as userManifests } from './users/manifests.js'; -import { manifests as userSectionManifests } from './user-section/manifests.js'; -import { manifests as currentUserManifests } from './current-user/manifests.js'; - -import { - UmbCurrentUserHistoryStore, - UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, -} from './current-user/current-user-history.store.js'; -import { UmbUserItemStore } from './users/repository/user-item.store.js'; -import { UmbUserGroupItemStore } from './user-groups/repository/user-group-item.store.js'; -import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; -import { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; - -import './users/components/index.js'; -import './user-groups/components/index.js'; - -export const manifests = [...userGroupManifests, ...userManifests, ...userSectionManifests, ...currentUserManifests]; - -export const onInit: UmbEntryPointOnInit = (host, extensionRegistry) => { - extensionRegistry.registerMany(manifests); - - new UmbUserItemStore(host); - new UmbUserGroupItemStore(host); - new UmbContextProviderController( - host, - UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, - new UmbCurrentUserHistoryStore() - ); -}; +export * from './users/components/index.js'; +export * from './user-groups/components/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/users/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/users/manifests.ts new file mode 100644 index 0000000000..75aecdcb8b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/users/manifests.ts @@ -0,0 +1,6 @@ +import { manifests as userGroupManifests } from './user-groups/manifests.js'; +import { manifests as userManifests } from './users/manifests.js'; +import { manifests as userSectionManifests } from './user-section/manifests.js'; +import { manifests as currentUserManifests } from './current-user/manifests.js'; + +export const manifests = [...userGroupManifests, ...userManifests, ...userSectionManifests, ...currentUserManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/users/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/users/umbraco-package.ts index b7745aca59..7418be463d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/users/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/users/umbraco-package.ts @@ -2,9 +2,9 @@ export const name = 'Umbraco.Core.UserManagement'; export const version = '0.0.1'; export const extensions = [ { - name: 'User Management Entry Point', - alias: 'Umb.EntryPoint.UserManagement', - type: 'entryPoint', - loader: () => import('./index.js'), + name: 'User Management Bundle', + alias: 'Umb.Bundle.UserManagement', + type: 'bundle', + loader: () => import('./manifests.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/users/user-groups/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/users/user-groups/repository/manifests.ts index 284f2eddfc..cc7861adc8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/users/user-groups/repository/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/users/user-groups/repository/manifests.ts @@ -1,6 +1,7 @@ import { UmbUserGroupRepository } from '../repository/user-group.repository.js'; +import { UmbUserGroupItemStore } from './user-group-item.store.js'; import { UmbUserGroupStore } from './user-group.store.js'; -import type { ManifestStore, ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestStore, ManifestRepository, ManifestItemStore } from '@umbraco-cms/backoffice/extension-registry'; export const USER_GROUP_REPOSITORY_ALIAS = 'Umb.Repository.UserGroup'; @@ -11,13 +12,18 @@ const repository: ManifestRepository = { class: UmbUserGroupRepository, }; -export const USER_GROUP_STORE_ALIAS = 'Umb.Store.UserGroup'; - const store: ManifestStore = { type: 'store', - alias: USER_GROUP_STORE_ALIAS, + alias: 'Umb.Store.UserGroup', name: 'User Group Store', class: UmbUserGroupStore, }; -export const manifests = [repository, store]; +const itemStore: ManifestItemStore = { + type: 'itemStore', + alias: 'Umb.ItemStore.UserGroup', + name: 'User Group Item Store', + class: UmbUserGroupItemStore, +}; + +export const manifests = [repository, store, itemStore]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/users/users/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/users/users/repository/manifests.ts index 91f6e33d3f..d762ce87fc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/users/users/repository/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/users/users/repository/manifests.ts @@ -1,6 +1,7 @@ import { UmbUserRepository } from '../repository/user.repository.js'; +import { UmbUserItemStore } from './user-item.store.js'; import { UmbUserStore } from './user.store.js'; -import type { ManifestStore, ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestStore, ManifestRepository, ManifestItemStore } from '@umbraco-cms/backoffice/extension-registry'; export const USER_REPOSITORY_ALIAS = 'Umb.Repository.User'; @@ -11,13 +12,18 @@ const repository: ManifestRepository = { class: UmbUserRepository, }; -export const USER_STORE_ALIAS = 'Umb.Store.User'; - const store: ManifestStore = { type: 'store', - alias: USER_STORE_ALIAS, + alias: 'Umb.Store.User', name: 'User Store', class: UmbUserStore, }; -export const manifests = [repository, store]; +const itemStore: ManifestItemStore = { + type: 'itemStore', + alias: 'Umb.ItemStore.User', + name: 'User Store', + class: UmbUserItemStore, +}; + +export const manifests = [repository, store, itemStore]; diff --git a/src/Umbraco.Web.UI.Client/src/shared/resources/resource.controller.ts b/src/Umbraco.Web.UI.Client/src/shared/resources/resource.controller.ts index 3b73d69660..63440f558b 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/resources/resource.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/resources/resource.controller.ts @@ -5,16 +5,16 @@ import { UmbNotificationOptions, } from '@umbraco-cms/backoffice/notification'; import { ApiError, CancelError, CancelablePromise } from '@umbraco-cms/backoffice/backend-api'; -import { UmbController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBaseController, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; import type { DataSourceResponse } from '@umbraco-cms/backoffice/repository'; -export class UmbResourceController extends UmbController { +export class UmbResourceController extends UmbBaseController { #promise: Promise; #notificationContext?: UmbNotificationContext; - constructor(host: UmbControllerHostElement, promise: Promise, alias?: string) { + constructor(host: UmbControllerHost, promise: Promise, alias?: string) { super(host, alias); this.#promise = promise; diff --git a/src/Umbraco.Web.UI.Client/src/shared/resources/tryExecuteAndNotify.function.ts b/src/Umbraco.Web.UI.Client/src/shared/resources/tryExecuteAndNotify.function.ts index e7c0e632e9..230be1ba43 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/resources/tryExecuteAndNotify.function.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/resources/tryExecuteAndNotify.function.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { UmbResourceController } from './resource.controller.js'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbNotificationOptions } from '@umbraco-cms/backoffice/notification'; export function tryExecuteAndNotify( - host: UmbControllerHostElement, + host: UmbControllerHost, resource: Promise, options?: UmbNotificationOptions ) { diff --git a/src/Umbraco.Web.UI.Client/src/shared/router/route.context.ts b/src/Umbraco.Web.UI.Client/src/shared/router/route.context.ts index e105dde24c..0dc4b48dea 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/router/route.context.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/router/route.context.ts @@ -1,19 +1,15 @@ import type { UmbRoute } from './route.interface.js'; import { generateRoutePathBuilder } from './generate-route-path-builder.function.js'; import type { IRoutingInfo, IRouterSlot } from '@umbraco-cms/backoffice/external/router-slot'; -import { - UmbContextConsumerController, - UmbContextProviderController, - UmbContextToken, -} from '@umbraco-cms/backoffice/context-api'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UmbBaseController, type UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UMB_MODAL_MANAGER_CONTEXT_TOKEN, UmbModalRouteRegistration } from '@umbraco-cms/backoffice/modal'; const EmptyDiv = document.createElement('div'); type UmbRoutePlusModalKey = UmbRoute & { __modalKey: string }; -export class UmbRouteContext { +export class UmbRouteContext extends UmbBaseController { #mainRouter: IRouterSlot; #modalRouter: IRouterSlot; #modalRegistrations: UmbModalRouteRegistration[] = []; @@ -24,10 +20,11 @@ export class UmbRouteContext { #activeModalPath?: string; constructor(host: UmbControllerHostElement, mainRouter: IRouterSlot, modalRouter: IRouterSlot) { + super(host); this.#mainRouter = mainRouter; this.#modalRouter = modalRouter; - new UmbContextProviderController(host, UMB_ROUTE_CONTEXT_TOKEN, this); - new UmbContextConsumerController(host, UMB_MODAL_MANAGER_CONTEXT_TOKEN, (context) => { + this.provideContext(UMB_ROUTE_CONTEXT_TOKEN, this); + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (context) => { this.#modalContext = context; this.#generateModalRoutes(); }); diff --git a/src/Umbraco.Web.UI.Client/src/tsconfig.json b/src/Umbraco.Web.UI.Client/src/tsconfig.json index 911e636ad2..cee90ae9fd 100644 --- a/src/Umbraco.Web.UI.Client/src/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/src/tsconfig.json @@ -6,7 +6,7 @@ "outDir": "../dist-cms", "rootDir": "./", "composite": true, - "sourceMap": true, + "sourceMap": false, "declaration": true, "resolveJsonModule": true, }, diff --git a/src/Umbraco.Web.UI.Client/storybook/stories/context-api.mdx b/src/Umbraco.Web.UI.Client/storybook/stories/context-api.mdx index 1765929ced..8b7b5c8fb5 100644 --- a/src/Umbraco.Web.UI.Client/storybook/stories/context-api.mdx +++ b/src/Umbraco.Web.UI.Client/storybook/stories/context-api.mdx @@ -4,15 +4,12 @@ import { Meta } from '@storybook/blocks'; # Context API -This element can be used as the base of any Element. -Do this if you need to Observe Data, Consume or Provide a Context API or use a Resource. -The Element implements the Controller Host and provides a few shortcut methods for initializing some Controllers. - -The methods are (_note this can be out of date, we need to look into how we can ensure this Doc originates from code._) +The Context API enables connections between Elements and APIs. +DOM structure defines the context of which an API is exposed for. APIs are provided via an element and can then be consumed by any decending element wthin. ### Consume a Context API. -From a Umbraco Element: +From a Umbraco Element or Umbraco Controller: ```ts this.consumeContext('requestThisContextAlias', (context) => { @@ -21,10 +18,10 @@ this.consumeContext('requestThisContextAlias', (context) => { }); ``` -Or with a Controller using a Controller Host(Umbraco Element): +Or with a Controller using a 'host' reference to Controller Host(Umbraco Element/Controller): ```ts -new UmbContextConsumerController(hostElement, 'requestThisContextAlias', (context) => { +new UmbContextConsumerController(host, 'requestThisContextAlias', (context) => { // Notice this is a subscription, as context might change or a new one appears. console.log("I've got the context", context); }); @@ -32,14 +29,14 @@ new UmbContextConsumerController(hostElement, 'requestThisContextAlias', (contex ### Provide a Context API. -From a Umbraco Element: +From a Umbraco Element or Umbraco Controller: ```ts this.provideContext('myContextAlias', new MyContextApi()); ``` -Or with a Controller using a Controller Host(Umbraco Element): +Or with a Controller using a 'host' reference to Controller Host(Umbraco Element/Controller): ```ts -new UmbContextProviderController(hostElement, 'myContextAlias', new MyContextApi()); +new UmbContextProviderController(host, 'myContextAlias', new MyContextApi()); ``` diff --git a/src/Umbraco.Web.UI.Client/storybook/stories/getting-started.mdx b/src/Umbraco.Web.UI.Client/storybook/stories/getting-started.mdx index 9769fd34c2..82005bb091 100644 --- a/src/Umbraco.Web.UI.Client/storybook/stories/getting-started.mdx +++ b/src/Umbraco.Web.UI.Client/storybook/stories/getting-started.mdx @@ -15,17 +15,16 @@ There is a few words that covers certain concepts, which is good to learn to eas - **Store** A API representing data, generally coming from the server. Most stores would talk with one or more resources. [Go to Store Guide](?path=/docs/guides-store--docs) - **State** A reactive container holding data, when data is changed all its Observables will be notified. -- **Observable** An observable is the hook for others to subscribe to the data or part of the data of a State. +- **Observable** An observable is the hook for others to subscribe to the data of a State. - **Observe** Observe is the term of what we do when subscriping to a Observable, We observe and observable. - **Context-API** The name of the system used to serve APIs(instances/classes) that for a certain context in the DOM. An API that is served via the Context-API is called a Context [Go to Context API Guide](?path=/docs/guides-context-api--docs) - **Context Provider** One that provides a class instance as a Context API. - **Context Consumer** One that consumer/subscripes to a class instance as a Context API. -- **Controller** An abstract term for a things that hooks into the lifecycle of a element. Many things in our system is Controllers, As a example notice these controllers: -- **Context Provider Controller** A Context Provider as a Controller, this make its easy to implement a Context Provider. -- **Context Consumer Controller** A Context Consumer as a Controller, this make its easy to implement a Context Consumer. -- **Observer Controller** A Controller for handling the observe subscription for a Observable. +- **Controller** An abstract term for a things that hooks into the lifecycle of a element. Many things in our system is Controllers. [Go to Controller Guide](?path=/docs/guides-controller--docs) +- **Umbraco Controller** Enables hosting controllers. Additionally it provides few shortcut methods for initializing core Umbraco Controllers. -- **Controller Host** The element that can host one or more controllers. -- **Umbraco Element** The UmbLitElement or UmbElemenMixin is a implementation of the Controller Host as an element. Using this as your base element provides a few methods that makes life easier. [Go to Umbraco Element Guide](?path=/docs/guides-umbraco-element--docs) +- **Controller Host** A class which can host controllers. +- **Controller Host Element** The element that can host controllers. +- **Umbraco Element** The `UmbLitElement` or `UmbElemenMixin` enables hosting controllers. Additionally it provides few shortcut methods for initializing core Umbraco Controllers. [Go to Umbraco Element Guide](?path=/docs/guides-umbraco-element--docs) diff --git a/src/Umbraco.Web.UI.Client/storybook/stories/umb-controller.mdx b/src/Umbraco.Web.UI.Client/storybook/stories/umb-controller.mdx new file mode 100644 index 0000000000..9c919807b1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/storybook/stories/umb-controller.mdx @@ -0,0 +1,36 @@ +import { Meta } from '@storybook/addon-docs'; + + + +# Umbraco Controller + +This class can be used as the base of any class. +This will enable Controllers to be hosted in this class. Additionally it provides few shortcut methods for initializing core Umbraco Controllers. + +```ts +observe(source: Observable, callback: (_value: T) => void, unique?: string): UmbObserverController + +provideContext(alias: string | UmbContextToken, instance: R): UmbContextProviderController + +consumeContext(alias: string | UmbContextToken, callback: UmbContextCallback): UmbContextConsumerController +``` + +Use these for an smooth consumption, like this request for a Context API using a simple string context, where the callback value is of an unknown type: + +```ts +this.consumeContext('requestThisContextAlias', (context) => { + // Notice this is a subscription, as context might change or a new one appears. + console.log("I've got the context", context); +}); +``` + +Or use the a Context Token to get a typed context: + +```ts +import { UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; + +this.consumeContext(UMB_NOTIFICATION_CONTEXT_TOKEN, (context) => { + // Notice this is a subscription, as context might change or a new one appears, but the value is strongly typed + console.log("I've got the context", context); +}); +``` diff --git a/src/Umbraco.Web.UI.Client/storybook/stories/umb-element.mdx b/src/Umbraco.Web.UI.Client/storybook/stories/umb-element.mdx index 0b33b86c97..bfca0776f6 100644 --- a/src/Umbraco.Web.UI.Client/storybook/stories/umb-element.mdx +++ b/src/Umbraco.Web.UI.Client/storybook/stories/umb-element.mdx @@ -4,11 +4,8 @@ import { Meta } from '@storybook/addon-docs'; # Umbraco Element -This element can be used as the base of any Element. -Do this if you need to Observe Data, Consume or Provide a Context API or use a Resource. -The Element implements the Controller Host and provides a few shortcut methods for initializing some Controllers. - -The methods are (_note this can be out of date, we need to look into how we can ensure this Doc originates from code._) +This element can be used as the base of any element. +This will enable Controllers to be hosted at this element. Additionally it provides few shortcut methods for initializing core Umbraco Controllers. ```ts observe(source: Observable, callback: (_value: T) => void, unique?: string): UmbObserverController @@ -18,7 +15,7 @@ provideContext(alias: string | UmbContextToken, instance: R): Um consumeContext(alias: string | UmbContextToken, callback: UmbContextCallback): UmbContextConsumerController ``` -Use these for an smooth consumption, like this request for a Context API using a simple string context, where the callback value is unknown +Use these for an smooth consumption, like this request for a Context API using a simple string context, where the callback value is of an unknown type: ```ts this.consumeContext('requestThisContextAlias', (context) => { @@ -27,12 +24,12 @@ this.consumeContext('requestThisContextAlias', (context) => { }); ``` -Or use the UmbContextToken type to define the type of the context, like this +Or use the a Context Token to get a typed context: ```ts -const contextAlias = new UmbContextToken('SomeTypeAlias', 'description of context for debugging purposes'); +import { UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; -this.consumeContext(contextAlias, (context) => { +this.consumeContext(UMB_NOTIFICATION_CONTEXT_TOKEN, (context) => { // Notice this is a subscription, as context might change or a new one appears, but the value is strongly typed console.log("I've got the context", context); }); diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 92e5cf7661..2c796237f3 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -19,16 +19,17 @@ "baseUrl": ".", "paths": { "@umbraco-cms/backoffice/external/lit": ["src/external/lit"], - "@umbraco-cms/backoffice/external/openid": ["src/external/openid"], - "@umbraco-cms/backoffice/external/rxjs": ["src/external/rxjs"], - "@umbraco-cms/backoffice/external/router-slot": ["src/external/router-slot"], - "@umbraco-cms/backoffice/external/uuid": ["src/external/uuid"], "@umbraco-cms/backoffice/external/lodash": ["src/external/lodash"], - "@umbraco-cms/backoffice/external/uui": ["src/external/uui"], "@umbraco-cms/backoffice/external/monaco-editor": ["src/external/monaco-editor"], + "@umbraco-cms/backoffice/external/openid": ["src/external/openid"], + "@umbraco-cms/backoffice/external/router-slot": ["src/external/router-slot"], + "@umbraco-cms/backoffice/external/rxjs": ["src/external/rxjs"], "@umbraco-cms/backoffice/external/tinymce": ["src/external/tinymce"], + "@umbraco-cms/backoffice/external/uui": ["src/external/uui"], + "@umbraco-cms/backoffice/external/uuid": ["src/external/uuid"], "@umbraco-cms/backoffice/backend-api": ["src/external/backend-api"], + "@umbraco-cms/backoffice/class-api": ["src/libs/class-api"], "@umbraco-cms/backoffice/context-api": ["src/libs/context-api"], "@umbraco-cms/backoffice/controller-api": ["src/libs/controller-api"], "@umbraco-cms/backoffice/element-api": ["src/libs/element-api"], @@ -61,13 +62,14 @@ "@umbraco-cms/backoffice/modal": ["src/packages/core/modal"], "@umbraco-cms/backoffice/notification": ["src/packages/core/notification"], "@umbraco-cms/backoffice/picker-input": ["src/packages/core/picker-input"], + "@umbraco-cms/backoffice/property-editor": ["src/packages/core/property-editor"], "@umbraco-cms/backoffice/section": ["src/packages/core/section"], "@umbraco-cms/backoffice/sorter": ["src/packages/core/sorter"], "@umbraco-cms/backoffice/store": ["src/packages/core/store"], + "@umbraco-cms/backoffice/themes": ["src/packages/core/themes/index.ts"], "@umbraco-cms/backoffice/tree": ["src/packages/core/tree"], "@umbraco-cms/backoffice/variant": ["src/packages/core/variant"], "@umbraco-cms/backoffice/workspace": ["src/packages/core/workspace"], - "@umbraco-cms/backoffice/property-editor": ["src/packages/core/property-editor"], "@umbraco-cms/backoffice/document": ["./src/packages/documents/documents/index.ts"], "@umbraco-cms/backoffice/document-blueprint": ["./src/packages/documents/document-blueprints/index.ts"], @@ -81,9 +83,8 @@ "@umbraco-cms/backoffice/data-type": ["./src/packages/settings/data-types/index.ts"], "@umbraco-cms/backoffice/language": ["./src/packages/settings/languages/index.ts"], - "@umbraco-cms/backoffice/logviewer":["./src/packages/settings/logviewer/index.ts"], + "@umbraco-cms/backoffice/logviewer": ["./src/packages/settings/logviewer/index.ts"], "@umbraco-cms/backoffice/relation-type": ["./src/packages/settings/relation-types/index.ts"], - "@umbraco-cms/backoffice/themes": ["./src/packages/settings/themes/index.ts"], "@umbraco-cms/backoffice/tags": ["./src/packages/tags/index.ts"], "@umbraco-cms/backoffice/partial-view": ["./src/packages/templating/partial-views/index.ts"], "@umbraco-cms/backoffice/stylesheet": ["./src/packages/templating/stylesheets/index.ts"], diff --git a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs index 60be185c72..e075293982 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -32,17 +32,18 @@ export default { imports: { 'src/': './src/', - '@umbraco-cms/backoffice/external/uui': './src/external/uui/index.ts', '@umbraco-cms/backoffice/external/lit': './src/external/lit/index.ts', - '@umbraco-cms/backoffice/external/openid': './src/external/openid/index.ts', - '@umbraco-cms/backoffice/external/rxjs': './src/external/rxjs/index.ts', - '@umbraco-cms/backoffice/external/router-slot': './src/external/router-slot/index.ts', - '@umbraco-cms/backoffice/external/uuid': './src/external/uuid/index.ts', '@umbraco-cms/backoffice/external/lodash': './src/external/lodash/index.ts', '@umbraco-cms/backoffice/external/monaco-editor': './src/external/monaco-editor/index.ts', + '@umbraco-cms/backoffice/external/openid': './src/external/openid/index.ts', + '@umbraco-cms/backoffice/external/router-slot': './src/external/router-slot/index.ts', + '@umbraco-cms/backoffice/external/rxjs': './src/external/rxjs/index.ts', '@umbraco-cms/backoffice/external/tinymce': './src/external/tinymce/index.ts', + '@umbraco-cms/backoffice/external/uui': './src/external/uui/index.ts', + '@umbraco-cms/backoffice/external/uuid': './src/external/uuid/index.ts', '@umbraco-cms/backoffice/backend-api': './src/external/backend-api/index.ts', + '@umbraco-cms/backoffice/class-api': './src/libs/class-api/index.ts', '@umbraco-cms/backoffice/context-api': './src/libs/context-api/index.ts', '@umbraco-cms/backoffice/controller-api': './src/libs/controller-api/index.ts', '@umbraco-cms/backoffice/element-api': './src/libs/element-api/index.ts', @@ -76,13 +77,14 @@ export default { '@umbraco-cms/backoffice/modal': './src/packages/core/modal/index.ts', '@umbraco-cms/backoffice/notification': './src/packages/core/notification/index.ts', '@umbraco-cms/backoffice/picker-input': './src/packages/core/picker-input/index.ts', + '@umbraco-cms/backoffice/property-editor': './src/packages/core/property-editor/index.ts', '@umbraco-cms/backoffice/section': './src/packages/core/section/index.ts', '@umbraco-cms/backoffice/sorter': './src/packages/core/sorter/index.ts', '@umbraco-cms/backoffice/store': './src/packages/core/store/index.ts', + '@umbraco-cms/backoffice/themes': './src/packages/core/themes/index.ts', '@umbraco-cms/backoffice/tree': './src/packages/core/tree/index.ts', '@umbraco-cms/backoffice/variant': './src/packages/core/variant/index.ts', '@umbraco-cms/backoffice/workspace': './src/packages/core/workspace/index.ts', - '@umbraco-cms/backoffice/property-editor': './src/packages/core/property-editor/index.ts', '@umbraco-cms/backoffice/document': './src/packages/documents/documents/index.ts', '@umbraco-cms/backoffice/document-blueprint': './src/packages/documents/document-blueprints/index.ts', @@ -98,7 +100,6 @@ export default { '@umbraco-cms/backoffice/language': './src/packages/settings/languages/index.ts', '@umbraco-cms/backoffice/logviewer': './src/packages/settings/logviewer/index.ts', '@umbraco-cms/backoffice/relation-type': './src/packages/settings/relation-types/index.ts', - '@umbraco-cms/backoffice/themes': './src/packages/settings/themes/index.ts', '@umbraco-cms/backoffice/tags': './src/packages/tags/index.ts', '@umbraco-cms/backoffice/partial-view': './src/packages/templating/partial-views/index.ts', '@umbraco-cms/backoffice/stylesheet': './src/packages/templating/stylesheets/index.ts',