diff --git a/src/Umbraco.Web.UI.Client/.eslintrc.json b/src/Umbraco.Web.UI.Client/.eslintrc.json index 4cd0e150db..5034e34797 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc.json +++ b/src/Umbraco.Web.UI.Client/.eslintrc.json @@ -33,7 +33,8 @@ "no-var": "error", "import/no-unresolved": "error", "import/order": "warn", - "local-rules/bad-type-import": "error" + "local-rules/bad-type-import": "error", + "local-rules/no-direct-api-import": "warn" }, "settings": { "import/parsers": { diff --git a/src/Umbraco.Web.UI.Client/.vscode/launch.json b/src/Umbraco.Web.UI.Client/.vscode/launch.json index 094ba1adaf..828c6a84f0 100644 --- a/src/Umbraco.Web.UI.Client/.vscode/launch.json +++ b/src/Umbraco.Web.UI.Client/.vscode/launch.json @@ -4,6 +4,12 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "command": "npx eslint", + "name": "Debug eslint", + "request": "launch", + "type": "node-terminal" + }, { "type": "chrome", "request": "launch", diff --git a/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs b/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs index eeddac50c8..a4d6a82658 100644 --- a/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs +++ b/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs @@ -4,8 +4,8 @@ * A eslint rule that ensures the use of the `import type` operator from the `src/core/models/index.ts` file. */ // eslint-disable-next-line no-undef -/** @type {import('eslint').Rule.RuleModule} */ module.exports = { + /** @type {import('eslint').Rule.RuleModule} */ 'bad-type-import': { meta: { type: 'problem', @@ -20,7 +20,7 @@ module.exports = { create: function (context) { return { ImportDeclaration: function (node) { - if (node.source.parent.importKind !== 'type' && (node.source.value.endsWith('/models') || node.source.value.endsWith('/generated-schema') || node.source.value === 'router-slot/model')) { + if (node.source.parent.importKind !== 'type' && (node.source.value.endsWith('/models') || node.source.value === 'router-slot/model')) { const sourceCode = context.getSourceCode(); const nodeSource = sourceCode.getText(node); context.report({ @@ -32,5 +32,37 @@ module.exports = { }, }; } + }, + + /** @type {import('eslint').Rule.RuleModule} */ + 'no-direct-api-import': { + meta: { + type: 'suggestion', + docs: { + description: 'Ensures that any API resources from the `@umbraco-cms/backend-api` module are not used directly. Instead you should use the `tryExecuteAndNotify` function from the `@umbraco-cms/resources` module.', + category: 'Best Practices', + recommended: true + }, + fixable: 'code', + schema: [], + }, + create: function (context) { + return { + // If methods called on *Resource classes are not already wrapped with `await tryExecuteAndNotify()`, then we should suggest to wrap them. + CallExpression: function (node) { + if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name.endsWith('Resource') && node.callee.property.type === 'Identifier' && node.callee.property.name !== 'constructor') { + const hasTryExecuteAndNotify = node.parent && node.parent.callee && (node.parent.callee.name === 'tryExecute' || node.parent.callee.name === 'tryExecuteAndNotify'); + if (!hasTryExecuteAndNotify) { + context.report({ + node, + message: 'Wrap this call with `tryExecuteAndNotify()`. Make sure to `await` the result.', + fix: fixer => [fixer.insertTextBefore(node, 'tryExecuteAndNotify(this, '), fixer.insertTextAfter(node, ')')], + }); + } + } + } + }; + + }, } }; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 6d2870387c..6606d01495 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -26,7 +26,7 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@babel/core": "^7.20.7", + "@babel/core": "^7.20.12", "@mdx-js/react": "^2.2.1", "@open-wc/testing": "^3.1.7", "@playwright/test": "^1.29.1", @@ -42,14 +42,14 @@ "@types/mocha": "^10.0.0", "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.48.0", - "@typescript-eslint/parser": "^5.47.0", + "@typescript-eslint/parser": "^5.48.0", "@web/dev-server-esbuild": "^0.3.3", "@web/dev-server-import-maps": "^0.0.7", "@web/test-runner": "^0.15.0", "@web/test-runner-playwright": "^0.9.0", - "babel-loader": "^9.1.0", + "babel-loader": "^9.1.2", "eslint": "^8.31.0", - "eslint-config-prettier": "^8.5.0", + "eslint-config-prettier": "^8.6.0", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.26.0", "eslint-plugin-lit": "^1.7.2", @@ -140,25 +140,25 @@ } }, "node_modules/@babel/core": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", - "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", "@babel/generator": "^7.20.7", "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", "@babel/helpers": "^7.20.7", "@babel/parser": "^7.20.7", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", + "@babel/traverse": "^7.20.12", "@babel/types": "^7.20.7", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", + "json5": "^2.2.2", "semver": "^6.3.0" }, "engines": { @@ -381,9 +381,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.7.tgz", - "integrity": "sha512-FNdu7r67fqMUSVuQpFQGE6BPdhJIhitoxhGzDbAXNcA07uoVG37fOiMk3OSV8rEICuyG6t8LGkd9EE64qIEoIA==", + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", @@ -392,7 +392,7 @@ "@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-validator-identifier": "^7.19.1", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", + "@babel/traverse": "^7.20.10", "@babel/types": "^7.20.7" }, "engines": { @@ -1961,9 +1961,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.20.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.8.tgz", - "integrity": "sha512-/RNkaYDeCy4MjyV70+QkSHhxbvj2JO/5Ft2Pa880qJOG8tWrqcT/wXUuCCv43yogfqPzHL77Xu101KQPf4clnQ==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", + "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.18.6", @@ -4081,24 +4081,6 @@ "node": ">=8" } }, - "node_modules/@storybook/builder-webpack4/node_modules/schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/@storybook/builder-webpack4/node_modules/tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -6425,53 +6407,6 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", - "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -6488,14 +6423,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.47.0.tgz", - "integrity": "sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz", + "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.47.0", - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/typescript-estree": "5.47.0", + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/typescript-estree": "5.48.0", "debug": "^4.3.4" }, "engines": { @@ -6515,13 +6450,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.0.tgz", - "integrity": "sha512-dvJab4bFf7JVvjPuh3sfBUWsiD73aiftKBpWSfi3sUkysDQ4W8x+ZcFpNp7Kgv0weldhpmMOZBjx1wKN8uWvAw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", + "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0" + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6558,7 +6493,7 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "node_modules/@typescript-eslint/types": { "version": "5.48.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", @@ -6571,7 +6506,7 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "node_modules/@typescript-eslint/typescript-estree": { "version": "5.48.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", @@ -6598,78 +6533,6 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.0.tgz", - "integrity": "sha512-eslFG0Qy8wpGzDdYKu58CEr3WLkjwC5Usa6XbuV89ce/yN5RITLe1O8e+WFEuxnfftHiJImkkOBADj58ahRxSg==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.0.tgz", - "integrity": "sha512-LxfKCG4bsRGq60Sqqu+34QT5qT2TEAHvSCCJ321uBWywgE2dS0LKcu5u+3sMGo+Vy9UmLOhdTw5JHzePV/1y4Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -6711,80 +6574,6 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", - "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", - "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/utils/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -6801,12 +6590,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.0.tgz", - "integrity": "sha512-ByPi5iMa6QqDXe/GmT/hR6MZtVPi0SqMQPDx15FczCBXJo/7M8T88xReOALAfpBLm+zxpPfmhuEvPb577JRAEg==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", + "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.47.0", + "@typescript-eslint/types": "5.48.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -8915,9 +8704,9 @@ "dev": true }, "node_modules/babel-loader": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", - "integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", "dev": true, "dependencies": { "find-cache-dir": "^3.3.2", @@ -11384,9 +11173,9 @@ } }, "node_modules/css-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -12705,9 +12494,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -15519,9 +15308,9 @@ } }, "node_modules/html-webpack-plugin/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -17101,9 +16890,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -25756,9 +25545,9 @@ } }, "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -28065,9 +27854,9 @@ "dev": true }, "node_modules/webpack/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -28694,25 +28483,25 @@ "dev": true }, "@babel/core": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", - "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", "@babel/generator": "^7.20.7", "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", "@babel/helpers": "^7.20.7", "@babel/parser": "^7.20.7", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", + "@babel/traverse": "^7.20.12", "@babel/types": "^7.20.7", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", + "json5": "^2.2.2", "semver": "^6.3.0" } }, @@ -28881,9 +28670,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.7.tgz", - "integrity": "sha512-FNdu7r67fqMUSVuQpFQGE6BPdhJIhitoxhGzDbAXNcA07uoVG37fOiMk3OSV8rEICuyG6t8LGkd9EE64qIEoIA==", + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.18.9", @@ -28892,7 +28681,7 @@ "@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-validator-identifier": "^7.19.1", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", + "@babel/traverse": "^7.20.10", "@babel/types": "^7.20.7" } }, @@ -29945,9 +29734,9 @@ } }, "@babel/traverse": { - "version": "7.20.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.8.tgz", - "integrity": "sha512-/RNkaYDeCy4MjyV70+QkSHhxbvj2JO/5Ft2Pa880qJOG8tWrqcT/wXUuCCv43yogfqPzHL77Xu101KQPf4clnQ==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", + "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", "dev": true, "requires": { "@babel/code-frame": "^7.18.6", @@ -31457,17 +31246,6 @@ "p-limit": "^2.2.0" } }, - "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - } - }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -33295,32 +33073,6 @@ "tsutils": "^3.21.0" }, "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", - "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0" - } - }, - "@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true - }, - "@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - } - }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -33333,25 +33085,25 @@ } }, "@typescript-eslint/parser": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.47.0.tgz", - "integrity": "sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz", + "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.47.0", - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/typescript-estree": "5.47.0", + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/typescript-estree": "5.48.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.0.tgz", - "integrity": "sha512-dvJab4bFf7JVvjPuh3sfBUWsiD73aiftKBpWSfi3sUkysDQ4W8x+ZcFpNp7Kgv0weldhpmMOZBjx1wKN8uWvAw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", + "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", "dev": true, "requires": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0" + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0" } }, "@typescript-eslint/type-utils": { @@ -33364,64 +33116,22 @@ "@typescript-eslint/utils": "5.48.0", "debug": "^4.3.4", "tsutils": "^3.21.0" - }, - "dependencies": { - "@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", - "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "@typescript-eslint/types": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.0.tgz", - "integrity": "sha512-eslFG0Qy8wpGzDdYKu58CEr3WLkjwC5Usa6XbuV89ce/yN5RITLe1O8e+WFEuxnfftHiJImkkOBADj58ahRxSg==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", + "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.0.tgz", - "integrity": "sha512-LxfKCG4bsRGq60Sqqu+34QT5qT2TEAHvSCCJ321uBWywgE2dS0LKcu5u+3sMGo+Vy9UmLOhdTw5JHzePV/1y4Q==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", + "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -33456,47 +33166,6 @@ "semver": "^7.3.7" }, "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", - "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0" - } - }, - "@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", - "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - } - }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -33509,12 +33178,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.0.tgz", - "integrity": "sha512-ByPi5iMa6QqDXe/GmT/hR6MZtVPi0SqMQPDx15FczCBXJo/7M8T88xReOALAfpBLm+zxpPfmhuEvPb577JRAEg==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", + "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", "dev": true, "requires": { - "@typescript-eslint/types": "5.47.0", + "@typescript-eslint/types": "5.48.0", "eslint-visitor-keys": "^3.3.0" } }, @@ -35360,9 +35029,9 @@ "dev": true }, "babel-loader": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", - "integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", "dev": true, "requires": { "find-cache-dir": "^3.3.2", @@ -37299,9 +36968,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -38350,9 +38019,9 @@ } }, "eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", "dev": true }, "eslint-import-resolver-node": { @@ -40431,9 +40100,9 @@ }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -41581,9 +41250,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "jsonfile": { @@ -48223,9 +47892,9 @@ }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -49797,9 +49466,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index f2231059ba..2f0ec3c4c4 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -39,7 +39,7 @@ "lint:fix": "npm run lint -- --fix", "format": "prettier 'src/**/*.ts'", "format:fix": "npm run format -- --write", - "generate:api": "openapi --input https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v11/dev/src/Umbraco.Cms.ManagementApi/OpenApi.json --output src/core/backend-api --postfix Resource --useOptions", + "generate:api": "openapi --input https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v13/dev/src/Umbraco.Cms.Api.Management/OpenApi.json --output src/core/backend-api --postfix Resource --useOptions", "generate:api-dev": "openapi --input http://localhost:9000/umbraco/swagger/v1/swagger.json --output src/core/backend-api --postfix Resource --useOptions", "storybook": "npm run wc-analyze && start-storybook -p 6006", "build-storybook": "npm run wc-analyze && build-storybook", @@ -71,7 +71,7 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@babel/core": "^7.20.7", + "@babel/core": "^7.20.12", "@mdx-js/react": "^2.2.1", "@open-wc/testing": "^3.1.7", "@playwright/test": "^1.29.1", @@ -87,14 +87,14 @@ "@types/mocha": "^10.0.0", "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.48.0", - "@typescript-eslint/parser": "^5.47.0", + "@typescript-eslint/parser": "^5.48.0", "@web/dev-server-esbuild": "^0.3.3", "@web/dev-server-import-maps": "^0.0.7", "@web/test-runner": "^0.15.0", "@web/test-runner-playwright": "^0.9.0", - "babel-loader": "^9.1.0", + "babel-loader": "^9.1.2", "eslint": "^8.31.0", - "eslint-config-prettier": "^8.5.0", + "eslint-config-prettier": "^8.6.0", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.26.0", "eslint-plugin-lit": "^1.7.2", diff --git a/src/Umbraco.Web.UI.Client/src/app.ts b/src/Umbraco.Web.UI.Client/src/app.ts index 1980d5f434..deac04fcb6 100644 --- a/src/Umbraco.Web.UI.Client/src/app.ts +++ b/src/Umbraco.Web.UI.Client/src/app.ts @@ -1,28 +1,29 @@ import './core/css/custom-properties.css'; + +// TODO: remove these imports when they are part of UUI +import '@umbraco-ui/uui-color-swatch'; +import '@umbraco-ui/uui-color-swatches'; import '@umbraco-ui/uui-modal'; import '@umbraco-ui/uui-modal-container'; import '@umbraco-ui/uui-modal-dialog'; import '@umbraco-ui/uui-modal-sidebar'; -import '@umbraco-ui/uui-color-swatch'; -import '@umbraco-ui/uui-color-swatches'; -import 'router-slot'; import 'element-internals-polyfill'; +import 'router-slot'; +import './auth'; -// TODO: remove these imports when they are part of UUI import type { Guard, IRoute } from 'router-slot/model'; import { UUIIconRegistryEssential } from '@umbraco-ui/uui'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { UmbIconStore } from '@umbraco-cms/stores/icon/icon.store'; +import { UmbLitElement } from './core/element/lit-element.element'; +import { tryExecuteAndNotify } from './core/resources/tryExecuteAndNotify.method'; import { OpenAPI, RuntimeLevel, ServerResource } from '@umbraco-cms/backend-api'; -import { UmbContextProviderMixin } from '@umbraco-cms/context-api'; - -import './auth'; +import { UmbIconStore } from '@umbraco-cms/stores/icon/icon.store'; @customElement('umb-app') -export class UmbApp extends UmbContextProviderMixin(LitElement) { +export class UmbApp extends UmbLitElement { static styles = css` :host { overflow: hidden; @@ -95,14 +96,8 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) { } private async _setInitStatus() { - try { - const serverStatus = await ServerResource.getServerStatus(); - if (serverStatus.serverStatus) { - this._runtimeLevel = serverStatus.serverStatus; - } - } catch (error) { - console.log(error); - } + const { data } = await tryExecuteAndNotify(this, ServerResource.getServerStatus()); + this._runtimeLevel = data?.serverStatus ?? RuntimeLevel.UNKNOWN; } private _redirect() { diff --git a/src/Umbraco.Web.UI.Client/src/auth/components/input-user-group/input-user-group.element.ts b/src/Umbraco.Web.UI.Client/src/auth/components/input-user-group/input-user-group.element.ts index b76fe404fc..f1133821f4 100644 --- a/src/Umbraco.Web.UI.Client/src/auth/components/input-user-group/input-user-group.element.ts +++ b/src/Umbraco.Web.UI.Client/src/auth/components/input-user-group/input-user-group.element.ts @@ -3,11 +3,10 @@ import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbInputListBase } from '../../../backoffice/shared/components/input-list-base/input-list-base'; import type { UserGroupEntity } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { UmbUserGroupStore } from 'src/backoffice/users/user-groups/user-group.store'; @customElement('umb-input-user-group') -export class UmbInputPickerUserGroupElement extends UmbObserverMixin(UmbInputListBase) { +export class UmbInputPickerUserGroupElement extends UmbInputListBase { static styles = [ UUITextStyles, css` @@ -53,7 +52,7 @@ export class UmbInputPickerUserGroupElement extends UmbObserverMixin(UmbInputLis private _observeUserGroups() { if (this.value.length > 0 && this._userGroupStore) { - this.observe>( + this.observe( this._userGroupStore.getByKeys(this.value), (userGroups) => (this._userGroups = userGroups) ); diff --git a/src/Umbraco.Web.UI.Client/src/auth/components/input-user/input-user.element.ts b/src/Umbraco.Web.UI.Client/src/auth/components/input-user/input-user.element.ts index 9e5b153439..dd99d8520d 100644 --- a/src/Umbraco.Web.UI.Client/src/auth/components/input-user/input-user.element.ts +++ b/src/Umbraco.Web.UI.Client/src/auth/components/input-user/input-user.element.ts @@ -2,12 +2,11 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing, PropertyValueMap } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbInputListBase } from '../../../backoffice/shared/components/input-list-base/input-list-base'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import type { UserEntity } from '@umbraco-cms/models'; import { UmbUserStore } from 'src/backoffice/users/users/user.store'; @customElement('umb-input-user') -export class UmbPickerUserElement extends UmbObserverMixin(UmbInputListBase) { +export class UmbPickerUserElement extends UmbInputListBase { static styles = [ UUITextStyles, css` @@ -56,6 +55,7 @@ export class UmbPickerUserElement extends UmbObserverMixin(UmbInputListBase) { private _observeUser() { if (!this._userStore) return; + // TODO: Fix type mismatch: this.observe>(this._userStore.getByKeys(this.value), (users) => { this._users = users; }); diff --git a/src/Umbraco.Web.UI.Client/src/auth/external-login-providers/external-login-provider-test.element.ts b/src/Umbraco.Web.UI.Client/src/auth/external-login-providers/external-login-provider-test.element.ts index 8d502d2e98..e9a3ccb26c 100644 --- a/src/Umbraco.Web.UI.Client/src/auth/external-login-providers/external-login-provider-test.element.ts +++ b/src/Umbraco.Web.UI.Client/src/auth/external-login-providers/external-login-provider-test.element.ts @@ -1,13 +1,10 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement } from 'lit/decorators.js'; -import { UmbContextProviderMixin, UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-external-login-provider-test') -export class UmbExternalLoginProviderTestElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbExternalLoginProviderTestElement extends UmbLitElement { static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/auth/external-login-providers/external-login-provider-test2.element.ts b/src/Umbraco.Web.UI.Client/src/auth/external-login-providers/external-login-provider-test2.element.ts index 955ad87749..a9ed172f2b 100644 --- a/src/Umbraco.Web.UI.Client/src/auth/external-login-providers/external-login-provider-test2.element.ts +++ b/src/Umbraco.Web.UI.Client/src/auth/external-login-providers/external-login-provider-test2.element.ts @@ -1,13 +1,10 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement } from 'lit/decorators.js'; -import { UmbContextProviderMixin, UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-external-login-provider-test2') -export class UmbExternalLoginProviderTest2Element extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbExternalLoginProviderTest2Element extends UmbLitElement { static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts index 3b99c25a8c..b7cdfde616 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -1,6 +1,6 @@ import { defineElement } from '@umbraco-ui/uui-base/lib/registration'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UmbModalService } from '../core/modal'; import { UmbNotificationService } from '../core/notification'; @@ -21,8 +21,6 @@ import { UmbDocumentBlueprintStore } from './documents/document-blueprints/docum import { UmbSectionStore } from './shared/components/section/section.store'; import { UmbDataTypeStore } from './settings/data-types/data-type.store'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; - // Domains import './settings'; import './documents'; @@ -33,9 +31,10 @@ import './users'; import './packages'; import './search'; import './shared'; +import { UmbLitElement } from '@umbraco-cms/element'; @defineElement('umb-backoffice') -export class UmbBackofficeElement extends UmbContextConsumerMixin(UmbContextProviderMixin(LitElement)) { +export class UmbBackofficeElement extends UmbLitElement { static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts index a428157da4..0b7e654bda 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts @@ -1,8 +1,9 @@ -import { UmbWorkspaceNodeContext } from '../../../shared/components/workspace/workspace-context/workspace-node.context'; +import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; import { UmbDocumentTypeStore, UmbDocumentTypeStoreItemType, } from 'src/backoffice/documents/document-types/document-type.store'; +import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; const DefaultDocumentTypeData = { key: '', @@ -15,11 +16,11 @@ const DefaultDocumentTypeData = { properties: [], } as UmbDocumentTypeStoreItemType; -export class UmbWorkspaceDocumentTypeContext extends UmbWorkspaceNodeContext< +export class UmbWorkspaceDocumentTypeContext extends UmbWorkspaceContentContext< UmbDocumentTypeStoreItemType, UmbDocumentTypeStore > { - constructor(target: HTMLElement, entityKey: string) { - super(target, DefaultDocumentTypeData, 'umbDocumentTypeStore', entityKey, 'documentType'); + constructor(host: UmbControllerHostInterface, entityKey: string) { + super(host, DefaultDocumentTypeData, 'umbDocumentTypeStore', entityKey, 'documentType'); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts index 24e56f4695..92ff854dd3 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts @@ -1,20 +1,16 @@ import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { distinctUntilChanged } from 'rxjs'; import { UmbWorkspaceDocumentTypeContext } from './document-type-workspace.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; import type { DocumentTypeDetails } from '@umbraco-cms/models'; import { UmbModalService } from 'src/core/modal'; - -import '../../../shared/property-editors/uis/icon-picker/property-editor-ui-icon-picker.element'; +import { UmbLitElement } from '@umbraco-cms/element'; +import type { UmbWorkspaceEntityElement } from 'src/backoffice/shared/components/workspace/workspace-entity-element.interface'; @customElement('umb-document-type-workspace') -export class UmbDocumentTypeWorkspaceElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbDocumentTypeWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement { static styles = [ UUITextStyles, css` @@ -75,17 +71,6 @@ export class UmbDocumentTypeWorkspaceElement extends UmbContextProviderMixin( }); } - connectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._workspaceContext?.connectedCallback(); - } - disconnectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._workspaceContext?.disconnectedCallback(); - } - protected _provideWorkspace() { if (this._entityKey) { this._workspaceContext = new UmbWorkspaceDocumentTypeContext(this, this._entityKey); @@ -97,8 +82,9 @@ export class UmbDocumentTypeWorkspaceElement extends UmbContextProviderMixin( private async _observeWorkspace() { if (!this._workspaceContext) return; - this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (data) => { - this._documentType = data; + this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (data) => { + // TODO: make method to identify if data is of type DocumentTypeDetails + this._documentType = (data as DocumentTypeDetails); }); } @@ -125,7 +111,7 @@ export class UmbDocumentTypeWorkspaceElement extends UmbContextProviderMixin( render() { return html` - + diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts index 94ee5a73d0..7bd5b8270e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts @@ -4,10 +4,9 @@ import { customElement } from 'lit/decorators.js'; import { UmbModalService } from '../../../../../../core/modal'; import { UmbDataTypeStore } from '../../../data-type.store'; import UmbTreeItemActionElement from '../../../../../shared/components/tree/action/tree-item-action.element'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; @customElement('umb-tree-action-data-type-delete') -export default class UmbTreeActionDataTypeDeleteElement extends UmbContextConsumerMixin(UmbTreeItemActionElement) { +export default class UmbTreeActionDataTypeDeleteElement extends UmbTreeItemActionElement { static styles = [UUITextStyles, css``]; private _modalService?: UmbModalService; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/tree-data-types.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/tree-data-types.element.ts index d85c3f0106..c80b46687e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/tree-data-types.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/tree-data-types.element.ts @@ -1,14 +1,14 @@ -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { customElement } from 'lit/decorators.js'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; import type { ManifestTreeItemAction } from '@umbraco-cms/models'; import '../../../shared/components/tree/navigator/tree-navigator.element'; import { UmbDataTypeStore } from 'src/backoffice/settings/data-types/data-type.store'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-tree-data-types') -export class UmbTreeDataTypesElement extends UmbContextProviderMixin(UmbContextConsumerMixin(LitElement)) { +export class UmbTreeDataTypesElement extends UmbLitElement { constructor() { super(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/workspace-view-data-type-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/workspace-view-data-type-edit.element.ts index 522c0581c4..aeecef13fa 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/workspace-view-data-type-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/workspace-view-data-type-edit.element.ts @@ -1,18 +1,18 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbModalService } from '../../../../../../core/modal'; import { UmbWorkspaceDataTypeContext } from '../../workspace-data-type.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import type { ManifestPropertyEditorUI, DataTypeDetails } from '@umbraco-cms/models'; +import { UmbDataTypeStoreItemType } from '../../../data-type.store'; +import type { DataTypeDetails, ManifestPropertyEditorUI } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; import '../../../../../shared/property-editors/shared/property-editor-config/property-editor-config.element'; import '../../../../../shared/components/ref-property-editor-ui/ref-property-editor-ui.element'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-view-data-type-edit') -export class UmbWorkspaceViewDataTypeEditElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbWorkspaceViewDataTypeEditElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -55,12 +55,15 @@ export class UmbWorkspaceViewDataTypeEditElement extends UmbContextConsumerMixin } private _observeDataType() { - if (!this._workspaceContext) return; + if (!this._workspaceContext) { + return; + } - this.observe(this._workspaceContext.data, (dataType) => { - this._dataType = dataType; + this.observe(this._workspaceContext.data, (dataType) => { + if (!dataType) return; - if (!this._dataType) return; + // TODO: handle if model is not of the type wanted. + this._dataType = dataType as DataTypeDetails; if (this._dataType.propertyEditorUIAlias !== this._propertyEditorUIAlias) { this._observePropertyEditorUI(this._dataType.propertyEditorUIAlias); @@ -75,7 +78,7 @@ export class UmbWorkspaceViewDataTypeEditElement extends UmbContextConsumerMixin private _observePropertyEditorUI(propertyEditorUIAlias: string | null) { if (!propertyEditorUIAlias) return; - this.observe( + this.observe( umbExtensionsRegistry.getByAlias(propertyEditorUIAlias), (propertyEditorUI) => { this._propertyEditorUIName = propertyEditorUI?.meta.label ?? propertyEditorUI?.name ?? ''; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/info/workspace-view-data-type-info.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/info/workspace-view-data-type-info.element.ts index 2c08af76d2..ea058ab821 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/info/workspace-view-data-type-info.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/info/workspace-view-data-type-info.element.ts @@ -1,14 +1,13 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { distinctUntilChanged } from 'rxjs'; import { UmbWorkspaceDataTypeContext } from '../../workspace-data-type.context'; import type { DataTypeDetails } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-view-data-type-info') -export class UmbWorkspaceViewDataTypeInfoElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbWorkspaceViewDataTypeInfoElement extends UmbLitElement { static styles = [UUITextStyles, css``]; @state() @@ -28,8 +27,12 @@ export class UmbWorkspaceViewDataTypeInfoElement extends UmbContextConsumerMixin private _observeDataType() { if (!this._workspaceContext) return; - this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (dataType) => { - this._dataType = dataType; + this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (dataType) => { + if(!dataType) return; + + // TODO: handle if model is not of the type wanted. + // TODO: Make method to identify wether data is of type DataTypeDetails + this._dataType = (dataType as DataTypeDetails); }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/workspace-data-type.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/workspace-data-type.context.ts index 5fd9b26be0..241f8de5ed 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/workspace-data-type.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/workspace-data-type.context.ts @@ -1,6 +1,7 @@ -import { UmbWorkspaceNodeContext } from '../../../shared/components/workspace/workspace-context/workspace-node.context'; +import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; import type { UmbDataTypeStore, UmbDataTypeStoreItemType } from 'src/backoffice/settings/data-types/data-type.store'; import type { DataTypeDetails } from '@umbraco-cms/models'; +import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; const DefaultDataTypeData = { key: '', @@ -14,9 +15,9 @@ const DefaultDataTypeData = { data: [], } as UmbDataTypeStoreItemType; -export class UmbWorkspaceDataTypeContext extends UmbWorkspaceNodeContext { - constructor(target: HTMLElement, entityKey: string) { - super(target, DefaultDataTypeData, 'umbDataTypeStore', entityKey, 'dataType'); +export class UmbWorkspaceDataTypeContext extends UmbWorkspaceContentContext { + constructor(host: UmbControllerHostInterface, entityKey: string) { + super(host, DefaultDataTypeData, 'umbDataTypeStore', entityKey, 'dataType'); } public setPropertyValue(propertyAlias: string, value: any) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/workspace-data-type.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/workspace-data-type.element.ts index df0c86c417..c0e232908f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/workspace-data-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/workspace-data-type.element.ts @@ -1,23 +1,17 @@ import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { distinctUntilChanged } from 'rxjs'; -import { UmbDataTypeStore } from '../data-type.store'; import { UmbWorkspaceDataTypeContext } from './workspace-data-type.context'; -import type { DataTypeDetails } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextProviderMixin, UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; +import { UmbLitElement } from '@umbraco-cms/element'; /** * @element umb-workspace-data-type * @description - Element for displaying a Data Type Workspace */ @customElement('umb-workspace-data-type') -export class UmbWorkspaceDataTypeElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbWorkspaceDataTypeElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -55,17 +49,6 @@ export class UmbWorkspaceDataTypeElement extends UmbContextProviderMixin( this.addEventListener('property-value-change', this._onPropertyValueChange); } - connectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._workspaceContext?.connectedCallback(); - } - disconnectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._workspaceContext?.disconnectedCallback(); - } - protected _provideWorkspace() { if (this._entityKey) { this._workspaceContext = new UmbWorkspaceDataTypeContext(this, this._entityKey); @@ -77,7 +60,7 @@ export class UmbWorkspaceDataTypeElement extends UmbContextProviderMixin( private _observeWorkspace() { if (!this._workspaceContext) return; - this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (dataType) => { + this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (dataType) => { if (dataType && dataType.name !== this._dataTypeName) { this._dataTypeName = dataType.name ?? ''; } @@ -102,9 +85,9 @@ export class UmbWorkspaceDataTypeElement extends UmbContextProviderMixin( render() { return html` - + - + `; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts index 9b833b3128..f0f0eb1a86 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts @@ -1,15 +1,14 @@ -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { isManifestElementNameType } from '@umbraco-cms/extensions-api'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { ManifestTypes } from '@umbraco-cms/models'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-extension-root-workspace') -export class UmbExtensionRootWorkspaceElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbExtensionRootWorkspaceElement extends UmbLitElement { @state() - private _extensions: Array = []; + private _extensions?: Array = undefined; connectedCallback(): void { super.connectedCallback(); @@ -17,14 +16,14 @@ export class UmbExtensionRootWorkspaceElement extends UmbContextConsumerMixin(Um } private _observeExtensions() { - this.observe>(umbExtensionsRegistry.extensions, (extensions) => { - this._extensions = [...extensions]; + this.observe(umbExtensionsRegistry.extensions, (extensions) => { + this._extensions = extensions || undefined; }); } render() { return html` - +

List of currently loaded extensions

@@ -35,7 +34,7 @@ export class UmbExtensionRootWorkspaceElement extends UmbContextConsumerMixin(Um Actions - ${this._extensions.map( + ${this._extensions?.map( (extension) => html` ${extension.type} @@ -53,7 +52,7 @@ export class UmbExtensionRootWorkspaceElement extends UmbContextConsumerMixin(Um )}
-
+ `; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/bulk-actions/collection-bulk-action-media-delete.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/bulk-actions/collection-bulk-action-media-delete.element.ts index acd1d0d490..6cf38a66e0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/bulk-actions/collection-bulk-action-media-delete.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/bulk-actions/collection-bulk-action-media-delete.element.ts @@ -1,13 +1,13 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import type { UmbCollectionContext } from '../collection.context'; import type { ManifestCollectionBulkAction } from '@umbraco-cms/models'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-collection-bulk-action-media-delete') -export class UmbCollectionBulkActionDeleteElement extends UmbContextConsumerMixin(LitElement) { +export class UmbCollectionBulkActionDeleteElement extends UmbLitElement { static styles = [UUITextStyles, css``]; // TODO: make a UmbCollectionContextMedia: diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-selection-actions.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-selection-actions.element.ts index cade122875..83121e2ee2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-selection-actions.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-selection-actions.element.ts @@ -1,13 +1,12 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import type { UmbCollectionContext } from './collection.context'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { MediaDetails } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-collection-selection-actions') -export class UmbCollectionSelectionActionsElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbCollectionSelectionActionsElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -57,11 +56,11 @@ export class UmbCollectionSelectionActionsElement extends UmbContextConsumerMixi if (!this._collectionContext) return; // TODO: Make sure it only updates on length change. - this.observe>(this._collectionContext.data, (mediaItems) => { + this.observe(this._collectionContext.data, (mediaItems) => { this._nodesLength = mediaItems.length; }); - this.observe>(this._collectionContext.selection, (selection) => { + this.observe(this._collectionContext.selection, (selection) => { this._selectionLength = selection.length; }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-toolbar.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-toolbar.element.ts index f745b1b5e0..fedfed7109 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-toolbar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-toolbar.element.ts @@ -1,15 +1,15 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { map } from 'rxjs'; import { TooltipMenuItem } from '../components/tooltip-menu'; import '../components/tooltip-menu/tooltip-menu.element'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import type { ManifestCollectionView } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-collection-toolbar') -export class UmbCollectionToolbarElement extends UmbObserverMixin(LitElement) { +export class UmbCollectionToolbarElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -71,15 +71,13 @@ export class UmbCollectionToolbarElement extends UmbObserverMixin(LitElement) { } private _observeCollectionViews() { - this.observe>( - umbExtensionsRegistry?.extensionsOfType('collectionView').pipe( + this.observe( + umbExtensionsRegistry.extensionsOfType('collectionView').pipe( map((extensions) => { return extensions.filter((extension) => extension.meta.entityType === 'media'); }) ), (layouts) => { - console.log('layouts', layouts); - if (layouts?.length === 0) return; this._layouts = layouts; if (!this._currentLayout) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts index 133325b3ca..8b290d8ad2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts @@ -1,21 +1,23 @@ -import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { ContentTreeItem } from '@umbraco-cms/backend-api'; -import { UmbContextConsumer } from '@umbraco-cms/context-api'; import { UmbTreeDataStore } from '@umbraco-cms/stores/store'; +import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; +import { UmbContextConsumerController } from 'src/core/context-api/consume/context-consumer.controller'; +import { UmbObserverController } from 'src/core/observable-api/observer.controller'; export class UmbCollectionContext< DataType extends ContentTreeItem, StoreType extends UmbTreeDataStore = UmbTreeDataStore > { - private _target: HTMLElement; + + private _host: UmbControllerHostInterface; private _entityKey: string | null; - protected _storeConsumer!: UmbContextConsumer; private _store?: StoreType; + protected _dataObserver?: UmbObserverController; private _data: BehaviorSubject> = new BehaviorSubject(>[]); public readonly data: Observable> = this._data.asObservable(); - protected _dataObserver?: Subscription; private _selection: BehaviorSubject> = new BehaviorSubject(>[]); public readonly selection: Observable> = this._selection.asObservable(); @@ -26,11 +28,11 @@ export class UmbCollectionContext< public readonly search: Observable = this._search.asObservable(); */ - constructor(target: HTMLElement, entityKey: string | null, storeAlias: string) { - this._target = target; + constructor(host: UmbControllerHostInterface, entityKey: string | null, storeAlias: string) { + this._host = host; this._entityKey = entityKey; - this._storeConsumer = new UmbContextConsumer(this._target, storeAlias, (_instance: StoreType) => { + new UmbContextConsumerController(this._host, storeAlias, (_instance: StoreType) => { this._store = _instance; if (!this._store) { // TODO: if we keep the type assumption of _store existing, then we should here make sure to break the application in a good way. @@ -40,14 +42,6 @@ export class UmbCollectionContext< }); } - connectedCallback() { - this._storeConsumer.hostConnected(); - } - - disconnectedCallback() { - this._storeConsumer.hostDisconnected(); - } - public getData() { return this._data.getValue(); } @@ -63,13 +57,19 @@ export class UmbCollectionContext< return; } + this._dataObserver?.destroy(); + if (this._entityKey) { - this._dataObserver = this._store.getTreeItemChildren(this._entityKey).subscribe((nodes) => { - this._data.next(nodes); + this._dataObserver = new UmbObserverController(this._host, this._store.getTreeItemChildren(this._entityKey), (nodes) => { + if(nodes) { + this._data.next(nodes); + } }); } else { - this._dataObserver = this._store.getTreeRoot().subscribe((nodes) => { - this._data.next(nodes); + this._dataObserver = new UmbObserverController(this._host, this._store.getTreeRoot(), (nodes) => { + if(nodes) { + this._data.next(nodes); + } }); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts index dcc36fda60..20bf799c50 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts @@ -1,5 +1,5 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, state, property } from 'lit/decorators.js'; import { map } from 'rxjs'; import './collection-selection-actions.element'; @@ -7,12 +7,12 @@ import './collection-toolbar.element'; import type { UmbCollectionContext } from './collection.context'; import { createExtensionElement } from '@umbraco-cms/extensions-api'; import type { ManifestCollectionView, MediaDetails } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; +import type { UmbObserverController } from 'src/core/observable-api/observer.controller'; @customElement('umb-collection') -export class UmbCollectionElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbCollectionElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -34,7 +34,7 @@ export class UmbCollectionElement extends UmbContextConsumerMixin(UmbObserverMix private _routes: Array = []; @state() - private _selection: Array = []; + private _selection?: Array | null; private _collectionContext?: UmbCollectionContext; @@ -48,7 +48,7 @@ export class UmbCollectionElement extends UmbContextConsumerMixin(UmbObserverMix this._observeCollectionViews(); } - private _collectionViewUnsubscribe?: () => void; + private _collectionViewUnsubscribe?: UmbObserverController>; constructor() { super(); @@ -62,35 +62,37 @@ export class UmbCollectionElement extends UmbContextConsumerMixin(UmbObserverMix private _observeCollectionContext() { if (!this._collectionContext) return; - this.observe>(this._collectionContext.selection, (selection) => { + this.observe(this._collectionContext.selection, (selection) => { this._selection = selection; }); } private _observeCollectionViews() { - this._collectionViewUnsubscribe?.(); - this._collectionViewUnsubscribe = this.observe>( + this._collectionViewUnsubscribe?.destroy(); + this._collectionViewUnsubscribe = this.observe( + // TODO: could we make some helper methods for this scenario: umbExtensionsRegistry?.extensionsOfType('collectionView').pipe( map((extensions) => { return extensions.filter((extension) => extension.meta.entityType === this._entityType); }) ), (views) => { - if (views?.length === 0) return; this._createRoutes(views); } ); } - private _createRoutes(views: ManifestCollectionView[]) { + private _createRoutes(views: ManifestCollectionView[] | null) { this._routes = []; - this._routes = views.map((view) => { - return { - path: `${view.meta.pathName}`, - component: () => createExtensionElement(view), - }; - }); + if(views) { + this._routes = views.map((view) => { + return { + path: `${view.meta.pathName}`, + component: () => createExtensionElement(view), + }; + }); + } this._routes.push({ path: '**', @@ -103,7 +105,7 @@ export class UmbCollectionElement extends UmbContextConsumerMixin(UmbObserverMix - ${this._selection.length > 0 + ${this._selection && this._selection.length > 0 ? html`` : nothing} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts index 9368ba1a88..697fa88192 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts @@ -1,18 +1,15 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import '../collection.element'; import { ifDefined } from 'lit-html/directives/if-defined.js'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { UmbMediaStore, UmbMediaStoreItemType } from 'src/backoffice/media/media/media.store'; import { UmbCollectionContext } from 'src/backoffice/shared/collection/collection.context'; import type { ManifestDashboardCollection } from '@umbraco-cms/models'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-dashboard-collection') -export class UmbDashboardCollectionElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbDashboardCollectionElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -42,14 +39,6 @@ export class UmbDashboardCollectionElement extends UmbContextProviderMixin( this._collectionContext = new UmbCollectionContext(this, null, manifestMeta.storeAlias); this.provideContext('umbCollectionContext', this._collectionContext); } - - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._collectionContext?.connectedCallback(); - } - disconnectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._collectionContext?.disconnectedCallback(); } render() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-grid.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-grid.element.ts index 262b022f94..dd7459a3b2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-grid.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-grid.element.ts @@ -1,14 +1,13 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import type { UmbCollectionContext } from '../collection.context'; import type { MediaDetails } from '@umbraco-cms/models'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-collection-view-media-grid') -export class UmbCollectionViewsMediaGridElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbCollectionViewsMediaGridElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -66,19 +65,19 @@ export class UmbCollectionViewsMediaGridElement extends UmbContextConsumerMixin( ]; @state() - private _mediaItems: Array = []; + private _mediaItems?: Array; @state() - private _selection: Array = []; + private _selection?: Array; private _collectionContext?: UmbCollectionContext; constructor() { super(); - document.addEventListener('dragenter', (e) => { + document.addEventListener('dragenter', () => { this.toggleAttribute('dragging', true); }); - document.addEventListener('dragleave', (e) => { + document.addEventListener('dragleave', () => { this.toggleAttribute('dragging', false); }); document.addEventListener('drop', (e) => { @@ -95,11 +94,11 @@ export class UmbCollectionViewsMediaGridElement extends UmbContextConsumerMixin( private _observeCollectionContext() { if (!this._collectionContext) return; - this.observe>(this._collectionContext.data, (mediaItems) => { + this.observe(this._collectionContext.data, (mediaItems) => { this._mediaItems = mediaItems.sort((a, b) => (a.hasChildren === b.hasChildren ? 0 : a ? -1 : 1)); }); - this.observe>(this._collectionContext.selection, (selection) => { + this.observe(this._collectionContext.selection, (selection) => { this._selection = selection; }); } @@ -118,7 +117,7 @@ export class UmbCollectionViewsMediaGridElement extends UmbContextConsumerMixin( } private _isSelected(mediaItem: MediaDetails) { - return this._selection.includes(mediaItem.key); + return this._selection?.includes(mediaItem.key); } private _renderMediaItem(item: MediaDetails) { @@ -126,7 +125,7 @@ export class UmbCollectionViewsMediaGridElement extends UmbContextConsumerMixin( //TODO: fix the file extension when media items have a file extension. return html` 0} + ?select-only=${this._selection && this._selection.length > 0} ?selected=${this._isSelected(item)} @open=${() => this._handleOpenItem(item)} @selected=${() => this._handleSelect(item)} @@ -145,11 +144,11 @@ export class UmbCollectionViewsMediaGridElement extends UmbContextConsumerMixin( label="Drop files here" accept="">
- ${repeat( + ${this._mediaItems ? repeat( this._mediaItems, (file, index) => file.key + index, (file) => this._renderMediaItem(file) - )} + ) : ''}
`; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-table.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-table.element.ts index 62c9fb45cb..069e3c8c9e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-table.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-table.element.ts @@ -1,20 +1,19 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import type { UmbCollectionContext } from '../collection.context'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { MediaDetails } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-collection-view-media-table') -export class UmbCollectionViewMediaTableElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbCollectionViewMediaTableElement extends UmbLitElement { static styles = [UUITextStyles, css``]; @state() - private _mediaItems: Array = []; + private _mediaItems?: Array; @state() - private _selection: Array = []; + private _selection?: Array; private _collectionContext?: UmbCollectionContext; @@ -29,11 +28,11 @@ export class UmbCollectionViewMediaTableElement extends UmbContextConsumerMixin( private _observeCollectionContext() { if (!this._collectionContext) return; - this.observe>(this._collectionContext.data, (nodes) => { + this.observe(this._collectionContext.data, (nodes) => { this._mediaItems = nodes; }); - this.observe>(this._collectionContext.selection, (selection) => { + this.observe(this._collectionContext.selection, (selection) => { this._selection = selection; }); } @@ -43,8 +42,8 @@ export class UmbCollectionViewMediaTableElement extends UmbContextConsumerMixin(

Selected Media Items:

    - ${this._selection.map((key) => { - const mediaItem = this._mediaItems.find((item) => item.key === key); + ${this._selection?.map((key) => { + const mediaItem = this._mediaItems?.find((item) => item.key === key); return html`
  • ${mediaItem?.name}
  • `; })}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-header-sections.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-header-sections.element.ts index 790732a0b7..7cd6c21bed 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-header-sections.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-header-sections.element.ts @@ -1,16 +1,13 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, CSSResultGroup, html, LitElement } from 'lit'; +import { css, CSSResultGroup, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; import { UmbSectionStore } from '../section/section.store'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; import type { ManifestSection } from '@umbraco-cms/models'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-backoffice-header-sections') -export class UmbBackofficeHeaderSections extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbBackofficeHeaderSections extends UmbLitElement { static styles: CSSResultGroup = [ UUITextStyles, css` @@ -90,7 +87,7 @@ export class UmbBackofficeHeaderSections extends UmbContextProviderMixin( private _observeSections() { if (!this._sectionStore) return; - this.observe(this._sectionStore?.getAllowed(), (allowedSections) => { + this.observe(this._sectionStore.getAllowed(), (allowedSections) => { this._sections = allowedSections; this._visibleSections = this._sections; }); @@ -99,7 +96,7 @@ export class UmbBackofficeHeaderSections extends UmbContextProviderMixin( private _observeCurrentSection() { if (!this._sectionStore) return; - this.observe(this._sectionStore.currentAlias, (currentSectionAlias) => { + this.observe(this._sectionStore.currentAlias, (currentSectionAlias) => { this._currentSectionAlias = currentSectionAlias; }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-main.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-main.element.ts index 5cbbd45eab..b9d6226019 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-main.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-main.element.ts @@ -1,18 +1,17 @@ import { defineElement } from '@umbraco-ui/uui-base/lib/registration'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { state } from 'lit/decorators.js'; import { IRoutingInfo } from 'router-slot'; import { UmbSectionStore } from '../section/section.store'; import { UmbSectionContext } from '../section/section.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { createExtensionElement } from '@umbraco-cms/extensions-api'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; import type { ManifestSection } from '@umbraco-cms/models'; import { UmbSectionElement } from 'src/backoffice/shared/components/section/section.element'; +import { UmbLitElement } from '@umbraco-cms/element'; @defineElement('umb-backoffice-main') -export class UmbBackofficeMain extends UmbContextProviderMixin(UmbContextConsumerMixin(UmbObserverMixin(LitElement))) { +export class UmbBackofficeMain extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -51,7 +50,7 @@ export class UmbBackofficeMain extends UmbContextProviderMixin(UmbContextConsume private async _observeSections() { if (!this._sectionStore) return; - this.observe(this._sectionStore?.getAllowed(), (sections) => { + this.observe(this._sectionStore.getAllowed(), (sections) => { this._sections = sections; if (!sections) return; this._createRoutes(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-modal-container.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-modal-container.element.ts index 367253b35b..a216a45f2d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-modal-container.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-modal-container.element.ts @@ -1,13 +1,12 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, CSSResultGroup, html, LitElement } from 'lit'; +import { css, CSSResultGroup, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { UmbModalHandler, UmbModalService } from '../../../../core/modal'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-backoffice-modal-container') -export class UmbBackofficeModalContainer extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbBackofficeModalContainer extends UmbLitElement { static styles: CSSResultGroup = [ UUITextStyles, css` @@ -18,7 +17,7 @@ export class UmbBackofficeModalContainer extends UmbContextConsumerMixin(UmbObse ]; @state() - private _modals: UmbModalHandler[] = []; + private _modals?: UmbModalHandler[]; private _modalService?: UmbModalService; @@ -34,7 +33,7 @@ export class UmbBackofficeModalContainer extends UmbContextConsumerMixin(UmbObse private _observeModals() { if (!this._modalService) return; - this.observe(this._modalService.modals, (modals) => { + this.observe(this._modalService.modals, (modals) => { this._modals = modals; }); } @@ -42,7 +41,7 @@ export class UmbBackofficeModalContainer extends UmbContextConsumerMixin(UmbObse render() { return html` - ${repeat(this._modals, (modalHandler) => html`${modalHandler.element}`)} + ${this._modals ? repeat(this._modals, (modalHandler) => html`${modalHandler.element}`) : ''} `; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-notification-container.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-notification-container.element.ts index af76d24a02..7540e565bf 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-notification-container.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-notification-container.element.ts @@ -1,13 +1,12 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, CSSResultGroup, html, LitElement } from 'lit'; +import { css, CSSResultGroup, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import type { UmbNotificationHandler, UmbNotificationService } from '../../../../core/notification'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-backoffice-notification-container') -export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbBackofficeNotificationContainer extends UmbLitElement { static styles: CSSResultGroup = [ UUITextStyles, css` @@ -24,7 +23,7 @@ export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin( ]; @state() - private _notifications: UmbNotificationHandler[] = []; + private _notifications?: UmbNotificationHandler[]; private _notificationService?: UmbNotificationService; @@ -40,7 +39,7 @@ export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin( private _observeNotifications() { if (!this._notificationService) return; - this.observe(this._notificationService.notifications, (notifications) => { + this.observe(this._notificationService.notifications, (notifications) => { this._notifications = notifications; }); } @@ -48,11 +47,11 @@ export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin( render() { return html` - ${repeat( + ${this._notifications ? repeat( this._notifications, (notification: UmbNotificationHandler) => notification.key, (notification) => html`${notification.element}` - )} + ) : ''} `; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts index 21da820074..8ee254f491 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts @@ -1,19 +1,18 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { customElement, property, state } from 'lit/decorators.js'; import { EMPTY, of, switchMap } from 'rxjs'; import { UmbDataTypeStore } from '../../../settings/data-types/data-type.store'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import type { ContentProperty, ManifestTypes } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import '../entity-property/entity-property.element'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-content-property') -export class UmbContentPropertyElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbContentPropertyElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -56,7 +55,7 @@ export class UmbContentPropertyElement extends UmbContextConsumerMixin(UmbObserv private _observeDataType() { if (!this._dataTypeStore || !this._property) return; - this.observe( + this.observe( this._dataTypeStore.getByKey(this._property.dataTypeKey).pipe( switchMap((dataType) => { if (!dataType?.propertyEditorUIAlias) return EMPTY; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/entity-property/entity-property.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/entity-property/entity-property.element.ts index ed4201c4b6..2b582722ec 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/entity-property/entity-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/entity-property/entity-property.element.ts @@ -1,5 +1,5 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement, PropertyValueMap } from 'lit'; +import { css, html, PropertyValueMap } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { UmbWorkspacePropertyContext } from './workspace-property.context'; import { createExtensionElement } from '@umbraco-cms/extensions-api'; @@ -8,9 +8,8 @@ import type { ManifestPropertyEditorUI, ManifestTypes } from '@umbraco-cms/model import '../../property-actions/shared/property-action-menu/property-action-menu.element'; import 'src/backoffice/shared/components/workspace/workspace-property-layout/workspace-property-layout.element'; -import { UmbContextProviderController } from 'src/core/context-api/provide/context-provider.controller'; -import { UmbControllerHostMixin } from 'src/core/controller/controller-host.mixin'; import { UmbObserverController } from 'src/core/observable-api/observer.controller'; +import { UmbLitElement } from '@umbraco-cms/element'; /** * @element umb-entity-property @@ -20,7 +19,7 @@ import { UmbObserverController } from 'src/core/observable-api/observer.controll // TODO: get rid of the other mixins: @customElement('umb-entity-property') -export class UmbEntityPropertyElement extends UmbControllerHostMixin(LitElement) { +export class UmbEntityPropertyElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -122,14 +121,13 @@ export class UmbEntityPropertyElement extends UmbControllerHostMixin(LitElement) // TODO: How to get proper default value? private _propertyContext = new UmbWorkspacePropertyContext(""); - private propertyEditorUIObserver?: UmbObserverController; + private propertyEditorUIObserver?: UmbObserverController; constructor() { super(); - // TODO: make it easier to create a provider, unless a context should just extends a provider-controller? - new UmbContextProviderController(this, 'umbPropertyContext', this._propertyContext); + this.provideContext('umbPropertyContext', this._propertyContext); this._observePropertyEditorUI(); this.addEventListener('property-editor-change', this._onPropertyEditorChange as any as EventListener); @@ -137,7 +135,7 @@ export class UmbEntityPropertyElement extends UmbControllerHostMixin(LitElement) private _observePropertyEditorUI() { this.propertyEditorUIObserver?.destroy(); - this.propertyEditorUIObserver = new UmbObserverController(this, umbExtensionsRegistry.getByAlias(this.propertyEditorUIAlias), (manifest) => { + this.propertyEditorUIObserver = new UmbObserverController(this, umbExtensionsRegistry.getByAlias(this.propertyEditorUIAlias), (manifest) => { if (manifest?.type === 'propertyEditorUI') { this._gotData(manifest); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/extension-slot/extension-slot.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/extension-slot/extension-slot.element.ts index dfe8bca542..7b1e9dc401 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/extension-slot/extension-slot.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/extension-slot/extension-slot.element.ts @@ -1,11 +1,11 @@ -import { LitElement, nothing } from 'lit'; +import { nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { map } from 'rxjs'; import { repeat } from 'lit/directives/repeat.js'; -import { ManifestTypes, umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { createExtensionElement, isManifestElementNameType } from '@umbraco-cms/extensions-api'; +import { ManifestBase, ManifestTypes, umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; +import { createExtensionElement } from '@umbraco-cms/extensions-api'; import { isManifestElementableType } from 'src/core/extensions-api/is-manifest-elementable-type.function'; +import { UmbLitElement } from '@umbraco-cms/element'; type InitializedExtensionItem = {alias: string, weight: number, component: HTMLElement|null} @@ -15,10 +15,10 @@ type InitializedExtensionItem = {alias: string, weight: number, component: HTMLE * @slot default - slot for inserting additional things into this slot. * @export * @class UmbExtensionSlot - * @extends {UmbObserverMixin(LitElement)} + * @extends {UmbLitElement} */ @customElement('umb-extension-slot') -export class UmbExtensionSlotElement extends UmbObserverMixin(LitElement) { +export class UmbExtensionSlotElement extends UmbLitElement { @state() private _extensions:InitializedExtensionItem[] = []; @@ -27,17 +27,8 @@ export class UmbExtensionSlotElement extends UmbObserverMixin(LitElement) { public type= ""; @property({ type: Object, attribute: false }) - public filter: (manifest:ManifestTypes) => boolean = () => true; - - constructor() { - super(); - - /* - this.extensionManager = new ExtensionManager(this, (x) => {x.meta.entityType === this.entityType}, (extensionManifests) => { - this._createElement(extensionManifests[0]); - }); - */ - } + public filter: (manifest: any) => boolean = () => true; + connectedCallback(): void { super.connectedCallback(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts index be301874b2..d4aaf7fac5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts @@ -8,7 +8,7 @@ import './content-property/content-property.element'; import './table/table.element'; import './code-block/code-block.element'; import './extension-slot/extension-slot.element'; -import './workspace/workspace-entity/workspace-entity.element'; +import './workspace/workspace-layout/workspace-layout.element'; import './section/section-main/section-main.element'; import './section/section-sidebar/section-sidebar.element'; import './section/section.element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-list-base/input-list-base.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-list-base/input-list-base.ts index 3b5e73fb7b..2ee8808dce 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-list-base/input-list-base.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-list-base/input-list-base.ts @@ -1,17 +1,18 @@ -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { property } from 'lit/decorators.js'; import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar'; import { UmbPickerData } from '../../../../core/modal/layouts/modal-layout-picker-base'; import { UmbModalService, UmbModalType } from '../../../../core/modal'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; //TODO: These should probably be imported dynamically. import '../../../../core/modal/layouts/picker-section/picker-layout-section.element'; import '../../../../core/modal/layouts/picker-user-group/picker-layout-user-group.element'; import '../../../../core/modal/layouts/picker-user/picker-layout-user.element'; +import { UmbLitElement } from '@umbraco-cms/element'; /** TODO: Make use of UUI FORM Mixin, to make it easily take part of a form. */ -export class UmbInputListBase extends UmbContextConsumerMixin(LitElement) { +export class UmbInputListBase extends UmbLitElement { + @property({ type: Array }) public value: Array = []; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-section/input-section.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-section/input-section.element.ts index b068e5473a..8d26ad21e2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-section/input-section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-section/input-section.element.ts @@ -57,6 +57,7 @@ export class UmbInputPickerSectionElement extends UmbInputListBase { selectionUpdated() { this._observeSections(); + // TODO: Use proper event class: this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts index b23dbd0f7b..504868b60f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts @@ -1,12 +1,10 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { IRoutingInfo } from 'router-slot'; import { first, map } from 'rxjs'; import { UmbSectionContext } from '../section.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { createExtensionElement } from '@umbraco-cms/extensions-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { ManifestDashboard, ManifestDashboardCollection, @@ -14,9 +12,10 @@ import type { ManifestWithMeta, } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-section-dashboards') -export class UmbSectionDashboardsElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbSectionDashboardsElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -47,7 +46,7 @@ export class UmbSectionDashboardsElement extends UmbContextConsumerMixin(UmbObse ]; @state() - private _dashboards: Array = []; + private _dashboards?: Array; @state() private _currentDashboardPathname = ''; @@ -73,19 +72,21 @@ export class UmbSectionDashboardsElement extends UmbContextConsumerMixin(UmbObse private _observeSectionContext() { if (!this._sectionContext) return; - this.observe(this._sectionContext.data.pipe(first()), (section) => { - this._currentSectionAlias = section.alias; - this._currentSectionPathname = section.meta.pathname; - this._observeDashboards(); + this.observe(this._sectionContext.data.pipe(first()), (section) => { + if(section) { + this._currentSectionAlias = section.alias; + this._currentSectionPathname = section.meta.pathname; + this._observeDashboards(); + } }); } private _observeDashboards() { if (!this._currentSectionAlias) return; - this.observe( + this.observe( umbExtensionsRegistry - ?.extensionsOfTypes(['dashboard', 'dashboardCollection']) + ?.extensionsOfTypes<(ManifestDashboard | ManifestDashboardCollection)>(['dashboard', 'dashboardCollection']) .pipe( map((extensions) => extensions.filter((extension) => @@ -94,8 +95,7 @@ export class UmbSectionDashboardsElement extends UmbContextConsumerMixin(UmbObse ) ), (dashboards) => { - if (dashboards?.length === 0) return; - this._dashboards = dashboards; + this._dashboards = dashboards || undefined; this._createRoutes(); } ); @@ -104,35 +104,38 @@ export class UmbSectionDashboardsElement extends UmbContextConsumerMixin(UmbObse private _createRoutes() { this._routes = []; - this._routes = this._dashboards.map((dashboard) => { - return { - path: `${dashboard.meta.pathname}`, - component: () => { - if (dashboard.type === 'dashboardCollection') { - return import('src/backoffice/shared/collection/dashboards/dashboard-collection.element'); - } - return createExtensionElement(dashboard); - }, - setup: (component: Promise | HTMLElement, info: IRoutingInfo) => { - this._currentDashboardPathname = info.match.route.path; - // When its using import, we get an element, when using createExtensionElement we get a Promise. - (component as any).manifest = dashboard; - if ((component as any).then) { - (component as any).then((el: any) => (el.manifest = dashboard)); - } - }, - }; - }); + if(this._dashboards) { + this._routes = this._dashboards.map((dashboard) => { + return { + path: `${dashboard.meta.pathname}`, + component: () => { + if (dashboard.type === 'dashboardCollection') { + return import('src/backoffice/shared/collection/dashboards/dashboard-collection.element'); + } + return createExtensionElement(dashboard); + }, + setup: (component: Promise | HTMLElement, info: IRoutingInfo) => { + this._currentDashboardPathname = info.match.route.path; + // When its using import, we get an element, when using createExtensionElement we get a Promise. + // TODO: this is a bit hacky, can we do it in a more appropriate way + (component as any).manifest = dashboard; + if ((component as any).then) { + (component as any).then((el: any) => (el.manifest = dashboard)); + } + }, + }; + }); - this._routes.push({ - path: '**', - redirectTo: this._dashboards?.[0]?.meta.pathname, - }); + this._routes.push({ + path: '**', + redirectTo: this._dashboards?.[0]?.meta.pathname, + }); + } } private _renderNavigation() { return html` - ${this._dashboards?.length > 1 + ${this._dashboards && this._dashboards.length > 1 ? html` ${this._dashboards.map( diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts index 5b129c5c4c..4019e31dfc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts @@ -1,15 +1,14 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbSectionContext } from '../section.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import type { ManifestSection } from '@umbraco-cms/models'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import '../../tree/context-menu/tree-context-menu.service'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-section-sidebar') -export class UmbSectionSidebarElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbSectionSidebarElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -49,7 +48,7 @@ export class UmbSectionSidebarElement extends UmbContextConsumerMixin(UmbObserve private _observeSectionContext() { if (!this._sectionContext) return; - this.observe(this._sectionContext.data, (section) => { + this.observe(this._sectionContext.data, (section) => { this._sectionLabel = section.meta.label || section.name; this._sectionPathname = section.meta.pathname; }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-trees/section-trees.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-trees/section-trees.element.ts index 4591e47aec..bb52efba9d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-trees/section-trees.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-trees/section-trees.element.ts @@ -1,21 +1,20 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { map, switchMap, EMPTY, of } from 'rxjs'; import { UmbSectionContext } from '../section.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; import '../../tree/tree-extension.element'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-section-trees') -export class UmbSectionTreesElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbSectionTreesElement extends UmbLitElement { static styles = [UUITextStyles]; @state() - private _treeAliases: Array = []; + private _treeAliases?: Array; private _sectionContext?: UmbSectionContext; @@ -31,30 +30,30 @@ export class UmbSectionTreesElement extends UmbContextConsumerMixin(UmbObserverM private _observeTrees() { if (!this._sectionContext) return; - this.observe( + this.observe( this._sectionContext?.data.pipe( switchMap((section) => { if (!section) return EMPTY; return ( umbExtensionsRegistry - ?.extensionsOfType('tree') + .extensionsOfType('tree') .pipe( map((trees) => trees.filter((tree) => tree.meta.sections.includes(section.alias)).map((tree) => tree.alias) ) - ) ?? of([]) + ) ); }) ), (treeAliases) => { - this._treeAliases = treeAliases; + this._treeAliases = treeAliases || undefined; } ); } render() { - return html`${this._treeAliases.map((treeAlias) => html``)} `; + return html`${this._treeAliases?.map((treeAlias) => html``)} `; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-views/section-views.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-views/section-views.element.ts index d9ed414926..2e8ef2a00b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-views/section-views.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-views/section-views.element.ts @@ -1,14 +1,14 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { EMPTY, map, of, Subscription, switchMap } from 'rxjs'; import { UmbSectionContext } from '../section.context'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { ManifestSectionView } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-section-views') -export class UmbSectionViewsElement extends UmbContextConsumerMixin(LitElement) { +export class UmbSectionViewsElement extends UmbLitElement { static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.context.ts index af65fc077f..549afb4808 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.context.ts @@ -18,15 +18,15 @@ export class UmbSectionContext { public readonly data = this._data.asObservable(); // TODO: what is the best context to put this in? - private _activeTree = new ReplaySubject(1); + private _activeTree = new ReplaySubject(1); public readonly activeTree = this._activeTree.asObservable(); // TODO: what is the best context to put this in? - private _activeTreeItem = new ReplaySubject(1); + private _activeTreeItem = new ReplaySubject(1); public readonly activeTreeItem = this._activeTreeItem.asObservable(); // TODO: what is the best context to put this in? - private _activeView = new ReplaySubject(1); + private _activeView = new ReplaySubject(1); public readonly activeView = this._activeView.asObservable(); constructor(section: ManifestSection) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts index d8ebda4a4a..9fac7d2b35 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts @@ -1,20 +1,20 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { map, switchMap, EMPTY, of } from 'rxjs'; import { IRoutingInfo } from 'router-slot'; +import type { UmbWorkspaceEntityElement } from '../workspace/workspace-entity-element.interface'; import { UmbSectionContext } from './section.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { createExtensionElement } from '@umbraco-cms/extensions-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { ManifestTree, ManifestSectionView, ManifestWorkspace } from '@umbraco-cms/models'; import './section-trees/section-trees.element.ts'; import './section-views/section-views.element.ts'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-section') -export class UmbSectionElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbSectionElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -36,12 +36,12 @@ export class UmbSectionElement extends UmbContextConsumerMixin(UmbObserverMixin( private _routes: Array = []; @state() - private _trees: Array = []; + private _trees?: Array; - private _workspaces: Array = []; + private _workspaces?: Array; @state() - private _views: Array = []; + private _views?: Array; private _sectionContext?: UmbSectionContext; @@ -60,7 +60,7 @@ export class UmbSectionElement extends UmbContextConsumerMixin(UmbObserverMixin( private _observeTrees() { if (!this._sectionContext) return; - this.observe( + this.observe( this._sectionContext?.data.pipe( switchMap((section) => { if (!section) return EMPTY; @@ -77,7 +77,7 @@ export class UmbSectionElement extends UmbContextConsumerMixin(UmbObserverMixin( } ); - this.observe(umbExtensionsRegistry?.extensionsOfType('workspace'), (workspaceExtensions) => { + this.observe(umbExtensionsRegistry.extensionsOfType('workspace'), (workspaceExtensions) => { this._workspaces = workspaceExtensions; this._createTreeRoutes(); }); @@ -96,9 +96,9 @@ export class UmbSectionElement extends UmbContextConsumerMixin(UmbObserverMixin( routes.push({ path: `${workspace.meta.entityType}/:key`, component: () => createExtensionElement(workspace), - setup: (component: Promise, info: IRoutingInfo) => { - component.then((el: HTMLElement) => { - (el as any).entityKey = info.match.params.key; + setup: (component: Promise, info: IRoutingInfo) => { + component.then((el) => { + el.entityKey = info.match.params.key; }); }, }); @@ -118,7 +118,7 @@ export class UmbSectionElement extends UmbContextConsumerMixin(UmbObserverMixin( private _observeViews() { if (!this._sectionContext) return; - this.observe( + this.observe( this._sectionContext.data.pipe( switchMap((section) => { if (!section) return EMPTY; @@ -137,9 +137,10 @@ export class UmbSectionElement extends UmbContextConsumerMixin(UmbObserverMixin( }) ), (views) => { - this._views = views; - if (this._views.length === 0) return; - this._createViewRoutes(); + if(views.length > 0) { + this._views = views; + this._createViewRoutes(); + } } ); } @@ -156,25 +157,29 @@ export class UmbSectionElement extends UmbContextConsumerMixin(UmbObserverMixin( }; }) ?? []; - this._routes.push({ - path: '**', - redirectTo: 'view/' + this._views?.[0]?.meta.pathname, - }); + if(this._views && this._views.length > 0) { + this._routes.push({ + path: '**', + redirectTo: 'view/' + this._views?.[0]?.meta.pathname, + }); + } } render() { return html` - ${this._trees.length > 0 + ${this._trees && this._trees.length > 0 ? html` - - - + + + ` : nothing} - ${this._views.length > 0 ? html`` : nothing} - ${this._routes.length > 0 - ? html` ` + ${this._views && this._views.length > 0 + ? html`` + : nothing} + ${this._routes && this._routes.length > 0 + ? html`` : nothing} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/action/tree-item-action.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/action/tree-item-action.element.ts index 4b3415713b..773916db86 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/action/tree-item-action.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/action/tree-item-action.element.ts @@ -1,11 +1,9 @@ -import { LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { UmbSectionContext } from '../../section/section.context'; import { UmbTreeContextMenuPageService } from '../context-menu/tree-context-menu-page.service'; import { UmbTreeContextMenuService } from '../context-menu/tree-context-menu.service'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { Entity, ManifestTreeItemAction, ManifestTree } from '@umbraco-cms/models'; +import { UmbLitElement } from '@umbraco-cms/element'; export type ActionPageEntity = { key: string; @@ -13,7 +11,7 @@ export type ActionPageEntity = { }; @customElement('umb-tree-item-action') -export default class UmbTreeItemActionElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export default class UmbTreeItemActionElement extends UmbLitElement { @property({ attribute: false }) public treeAction?: ManifestTreeItemAction; @@ -49,7 +47,7 @@ export default class UmbTreeItemActionElement extends UmbContextConsumerMixin(Um private _observeEntity() { if (!this._actionPageService) return; - this.observe(this._actionPageService.entity, (entity) => { + this.observe(this._actionPageService.entity, (entity) => { this._entity = entity; }); } @@ -57,7 +55,7 @@ export default class UmbTreeItemActionElement extends UmbContextConsumerMixin(Um private _observeActiveTree() { if (!this._sectionContext) return; - this.observe(this._sectionContext.activeTree, (tree) => { + this.observe(this._sectionContext.activeTree, (tree) => { this._activeTree = tree; }); } @@ -65,7 +63,7 @@ export default class UmbTreeItemActionElement extends UmbContextConsumerMixin(Um private _observeActiveTreeItem() { if (!this._sectionContext) return; - this.observe(this._sectionContext.activeTreeItem, (treeItem) => { + this.observe(this._sectionContext.activeTreeItem, (treeItem) => { this._activeTreeItem = treeItem; }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu-page-action-list.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu-page-action-list.element.ts index ca8d0cede6..47a9d3065a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu-page-action-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu-page-action-list.element.ts @@ -1,15 +1,14 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; import { map } from 'rxjs'; import { UmbSectionContext } from '../../section/section.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { Entity, ManifestTreeItemAction, ManifestTree } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-tree-context-menu-page-action-list') -export class UmbTreeContextMenuPageActionListElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbTreeContextMenuPageActionListElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -29,7 +28,7 @@ export class UmbTreeContextMenuPageActionListElement extends UmbContextConsumerM ]; @state() - private _actions: Array = []; + private _actions?: Array; @state() private _activeTree?: ManifestTree; @@ -53,12 +52,12 @@ export class UmbTreeContextMenuPageActionListElement extends UmbContextConsumerM private _observeTreeItemActions() { if (!this._sectionContext) return; - this.observe( + this.observe( umbExtensionsRegistry .extensionsOfType('treeItemAction') .pipe(map((actions) => actions.filter((action) => action.meta.trees.includes(this._activeTree?.alias || '')))), (actions) => { - this._actions = actions; + this._actions = actions || undefined; } ); } @@ -67,7 +66,7 @@ export class UmbTreeContextMenuPageActionListElement extends UmbContextConsumerM if (!this._sectionContext) return; this.observe(this._sectionContext.activeTree, (tree) => { - this._activeTree = tree; + this._activeTree = tree || undefined; }); } @@ -75,12 +74,12 @@ export class UmbTreeContextMenuPageActionListElement extends UmbContextConsumerM if (!this._sectionContext) return; this.observe(this._sectionContext.activeTreeItem, (treeItem) => { - this._activeTreeItem = treeItem; + this._activeTreeItem = treeItem || undefined; }); } private _renderActions() { - return this._actions.map((action) => { + return this._actions?.map((action) => { return html` `; }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu-page.service.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu-page.service.ts index 03a190e3fb..fa99c9a379 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu-page.service.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu-page.service.ts @@ -1,12 +1,12 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, LitElement, nothing, PropertyValueMap } from 'lit'; +import { css, nothing, PropertyValueMap } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { BehaviorSubject, Observable } from 'rxjs'; import UmbTreeItemActionElement, { ActionPageEntity } from '../action/tree-item-action.element'; -import { UmbContextProviderMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-tree-context-menu-page-service') -export class UmbTreeContextMenuPageService extends UmbContextProviderMixin(LitElement) { +export class UmbTreeContextMenuPageService extends UmbLitElement { static styles = [UUITextStyles, css``]; @property({ type: Object }) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu.service.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu.service.ts index 837e75b2a9..f21f2d833a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu.service.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/context-menu/tree-context-menu.service.ts @@ -1,11 +1,11 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { ActionPageEntity } from '../action/tree-item-action.element'; -import { UmbContextProviderMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-tree-context-menu-service') -export class UmbTreeContextMenuService extends UmbContextProviderMixin(LitElement) { +export class UmbTreeContextMenuService extends UmbLitElement { static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/navigator/tree-navigator.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/navigator/tree-navigator.element.ts index e2e61b7092..d4e4016394 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/navigator/tree-navigator.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/navigator/tree-navigator.element.ts @@ -1,19 +1,19 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { repeat } from 'lit/directives/repeat.js'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { UmbSectionContext } from '../../section/section.context'; import { UmbTreeContext } from '../tree.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; -import type { Entity, ManifestSection, ManifestTree } from '@umbraco-cms/models'; +import type { Entity, ManifestTree } from '@umbraco-cms/models'; import { UmbTreeDataStore } from '@umbraco-cms/stores/store'; import '../tree-item.element'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { DocumentTreeItem } from '@umbraco-cms/backend-api'; @customElement('umb-tree-navigator') -export class UmbTreeNavigator extends UmbContextConsumerMixin(UmbContextProviderMixin(UmbObserverMixin(LitElement))) { +export class UmbTreeNavigator extends UmbLitElement { static styles = [UUITextStyles, css``]; private _storeContextAlias = ''; @@ -31,7 +31,7 @@ export class UmbTreeNavigator extends UmbContextConsumerMixin(UmbContextProvider private _loading = true; @state() - private _items: Entity[] = []; + private _items: DocumentTreeItem[] = []; @state() private _tree?: ManifestTree; @@ -39,7 +39,7 @@ export class UmbTreeNavigator extends UmbContextConsumerMixin(UmbContextProvider @state() private _href?: string; - private _store?: UmbTreeDataStore; + private _store?: UmbTreeDataStore; private _sectionContext?: UmbSectionContext; constructor() { @@ -77,7 +77,7 @@ export class UmbTreeNavigator extends UmbContextConsumerMixin(UmbContextProvider this._loading = true; - this.observe(this._store.getTreeRoot(), (rootItems) => { + this.observe(this._store.getTreeRoot(), (rootItems) => { if (rootItems?.length === 0) return; this._items = rootItems; this._loading = false; @@ -87,7 +87,7 @@ export class UmbTreeNavigator extends UmbContextConsumerMixin(UmbContextProvider private _observeSection() { if (!this._sectionContext) return; - this.observe(this._sectionContext?.data, (section) => { + this.observe(this._sectionContext?.data, (section) => { this._href = this._constructPath(section.meta.pathname, this._tree?.meta.rootNodeEntityType); }); } @@ -110,11 +110,12 @@ export class UmbTreeNavigator extends UmbContextConsumerMixin(UmbContextProvider } private _renderRootItems() { + // TODO: Fix Type Mismatch ` as Entity` in this template: return html` ${repeat( this._items, (item) => item.key, - (item) => html`` + (item) => html`` )} `; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree-item.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree-item.element.ts index be5cd17df5..93bff33725 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree-item.element.ts @@ -1,30 +1,29 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { UUIMenuItemEvent } from '@umbraco-ui/uui'; -import { map } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { repeat } from 'lit/directives/repeat.js'; import { UmbSectionContext } from '../section/section.context'; import type { UmbTreeContextBase } from './tree.context'; import { UmbTreeContextMenuService } from './context-menu/tree-context-menu.service'; -import type { Entity, ManifestSection } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import type { Entity } from '@umbraco-cms/models'; import { UmbTreeDataStore } from '@umbraco-cms/stores/store'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-tree-item') -export class UmbTreeItem extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbTreeItem extends UmbLitElement { static styles = [UUITextStyles, css``]; @property({ type: Object, attribute: false }) treeItem!: Entity; @state() - private _childItems: Entity[] = []; + private _childItems?: Entity[]; @state() - private _href? = ''; + private _href?:string; @state() private _loading = false; @@ -87,26 +86,26 @@ export class UmbTreeItem extends UmbContextConsumerMixin(UmbObserverMixin(LitEle private _observeSection() { if (!this._sectionContext) return; - this.observe(this._sectionContext?.data, (section) => { - this._href = this._constructPath(section.meta.pathname, this.treeItem.type, this.treeItem.key); + this.observe(this._sectionContext?.data, (section) => { + this._href = this._constructPath(section?.meta.pathname || '', this.treeItem.type, this.treeItem.key); }); } private _observeSelectable() { if (!this._treeContext) return; - this.observe(this._treeContext.selectable, (value) => { - this._selectable = value; + this.observe(this._treeContext.selectable, (value) => { + this._selectable = value || false; }); } private _observeIsSelected() { if (!this._treeContext) return; - this.observe( + this.observe( this._treeContext.selection.pipe(map((keys) => keys?.includes(this.treeItem.key))), (isSelected) => { - this._selected = isSelected; + this._selected = isSelected || false; } ); } @@ -114,8 +113,8 @@ export class UmbTreeItem extends UmbContextConsumerMixin(UmbObserverMixin(LitEle private _observeActiveTreeItem() { if (!this._sectionContext) return; - this.observe(this._sectionContext?.activeTreeItem, (treeItem) => { - this._isActive = treeItem.key === this.treeItem.key; + this.observe(this._sectionContext?.activeTreeItem, (treeItem) => { + this._isActive = treeItem?.key === this.treeItem.key; }); } @@ -126,7 +125,7 @@ export class UmbTreeItem extends UmbContextConsumerMixin(UmbObserverMixin(LitEle private _onShowChildren(event: UUIMenuItemEvent) { event.stopPropagation(); - if (this._childItems.length > 0) return; + if (this._childItems && this._childItems.length > 0) return; this._observeChildren(); } @@ -135,8 +134,8 @@ export class UmbTreeItem extends UmbContextConsumerMixin(UmbObserverMixin(LitEle this._loading = true; - this.observe(this._store.getTreeItemChildren(this.treeItem.key), (childItems) => { - if (childItems?.length === 0) return; + // TODO: we should do something about these types, stop having our own version of Entity. + this.observe(this._store.getTreeItemChildren(this.treeItem.key) as Observable, (childItems) => { this._childItems = childItems; this._loading = false; }); @@ -144,11 +143,11 @@ export class UmbTreeItem extends UmbContextConsumerMixin(UmbObserverMixin(LitEle private _renderChildItems() { return html` - ${repeat( + ${this._childItems ? repeat( this._childItems, (item) => item.key, (item) => html`` - )} + ) : ''} `; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts index e2f1ecd361..0e498ffe25 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts @@ -1,16 +1,16 @@ -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { when } from 'lit-html/directives/when.js'; import { customElement, property, state } from 'lit/decorators.js'; import { map } from 'rxjs'; import { UmbTreeContextBase } from './tree.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; import type { ManifestTree } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; import { UmbDataStore } from '@umbraco-cms/stores/store'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { UmbContextProviderController } from 'src/core/context-api/provide/context-provider.controller'; @customElement('umb-tree') -export class UmbTreeElement extends UmbContextProviderMixin(UmbContextConsumerMixin(UmbObserverMixin(LitElement))) { +export class UmbTreeElement extends UmbLitElement { private _alias = ''; @property({ type: String, reflect: true }) get alias() { @@ -55,6 +55,7 @@ export class UmbTreeElement extends UmbContextProviderMixin(UmbContextConsumerMi private _tree?: ManifestTree; private _treeContext?: UmbTreeContextBase; + private _treeContextProvider?: UmbContextProviderController; connectedCallback(): void { super.connectedCallback(); @@ -64,34 +65,35 @@ export class UmbTreeElement extends UmbContextProviderMixin(UmbContextConsumerMi private _observeTree() { if (!this.alias) return; - this.observe( + this.observe( umbExtensionsRegistry .extensionsOfType('tree') .pipe(map((trees) => trees.find((tree) => tree.alias === this.alias))), - (tree) => { - if (this._tree?.alias === tree.alias) return; - + (tree => { this._tree = tree; - this._provideTreeContext(); - - if (this._tree.meta.storeContextAlias) { + if(tree) { + this._provideTreeContext(); this._provideStore(); } } - ); + )); } private _provideTreeContext() { if (!this._tree || this._treeContext) return; + // TODO: if a new tree comes around, which is different, then we should clean up and re provide. + this._treeContext = new UmbTreeContextBase(this._tree); this._treeContext.setSelectable(this.selectable); this._treeContext.setSelection(this.selection); - + this.provideContext('umbTreeContext', this._treeContext); } private _provideStore() { + // TODO: Clean up store, if already existing. + if (!this._tree?.meta.storeContextAlias) return; this.consumeContext(this._tree.meta.storeContextAlias, (store: UmbDataStore) => diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/actions/save/workspace-action-node-save.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/actions/save/workspace-action-node-save.element.ts index f6d07d1ebd..2e22762721 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/actions/save/workspace-action-node-save.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/actions/save/workspace-action-node-save.element.ts @@ -1,20 +1,20 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import type { UUIButtonState } from '@umbraco-ui/uui'; -import type { UmbWorkspaceNodeContext } from '../../workspace-context/workspace-node.context'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import type { UmbWorkspaceContentContext } from '../../workspace-content/workspace-content.context'; +import { UmbLitElement } from '@umbraco-cms/element'; import type { ManifestWorkspaceAction } from '@umbraco-cms/models'; @customElement('umb-workspace-action-node-save') -export class UmbWorkspaceActionNodeSaveElement extends UmbContextConsumerMixin(LitElement) { +export class UmbWorkspaceActionNodeSaveElement extends UmbLitElement { static styles = [UUITextStyles, css``]; @state() private _saveButtonState?: UUIButtonState; - private _workspaceContext?: UmbWorkspaceNodeContext; + private _workspaceContext?: UmbWorkspaceContentContext; public manifest?: ManifestWorkspaceAction; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts index b6240ffbe0..879e2a0683 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts @@ -1,20 +1,17 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement } from 'lit/decorators.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; -import type { UmbWorkspaceNodeContext } from '../../../workspace-context/workspace-node.context'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import type { UmbWorkspaceContentContext } from '../../workspace-content.context'; import { UmbCollectionContext } from 'src/backoffice/shared/collection/collection.context'; import { UmbMediaStore, UmbMediaStoreItemType } from 'src/backoffice/media/media/media.store'; import 'src/backoffice/shared/components/content-property/content-property.element'; import 'src/backoffice/shared/collection/dashboards/dashboard-collection.element'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-view-collection') -export class UmbWorkspaceViewCollectionElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbWorkspaceViewCollectionElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -25,7 +22,7 @@ export class UmbWorkspaceViewCollectionElement extends UmbContextProviderMixin( `, ]; - private _workspaceContext?: UmbWorkspaceNodeContext; + private _workspaceContext?: UmbWorkspaceContentContext; private _collectionContext?: UmbCollectionContext; @@ -38,17 +35,6 @@ export class UmbWorkspaceViewCollectionElement extends UmbContextProviderMixin( }); } - connectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._collectionContext?.connectedCallback(); - } - disconnectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._collectionContext?.disconnectedCallback(); - } - protected _provideWorkspace() { if (this._workspaceContext?.entityKey != null) { this._collectionContext = new UmbCollectionContext( @@ -56,7 +42,6 @@ export class UmbWorkspaceViewCollectionElement extends UmbContextProviderMixin( this._workspaceContext.entityKey, this._workspaceContext.getStore().storeAlias ); - this._collectionContext.connectedCallback(); this.provideContext('umbCollectionContext', this._collectionContext); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/edit/workspace-view-content-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/edit/workspace-view-content-edit.element.ts index 32d6bb3f68..c45c5074b1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/edit/workspace-view-content-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/edit/workspace-view-content-edit.element.ts @@ -1,16 +1,15 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; import { distinctUntilChanged } from 'rxjs'; -import type { UmbWorkspaceNodeContext } from '../../../workspace-context/workspace-node.context'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import type { UmbWorkspaceContentContext } from '../../workspace-content.context'; import type { ContentProperty, ContentPropertyData, DocumentDetails, MediaDetails } from '@umbraco-cms/models'; import '../../../../content-property/content-property.element'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-view-content-edit') -export class UmbWorkspaceViewContentEditElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbWorkspaceViewContentEditElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -27,7 +26,7 @@ export class UmbWorkspaceViewContentEditElement extends UmbContextConsumerMixin( @state() _data: ContentPropertyData[] = []; - private _workspaceContext?: UmbWorkspaceNodeContext; + private _workspaceContext?: UmbWorkspaceContentContext; constructor() { super(); @@ -41,7 +40,7 @@ export class UmbWorkspaceViewContentEditElement extends UmbContextConsumerMixin( private _observeContent() { if (!this._workspaceContext) return; - this.observe( + this.observe( this._workspaceContext.data.pipe(distinctUntilChanged()), (content) => { this._properties = content.properties; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/info/workspace-view-content-info.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/info/workspace-view-content-info.element.ts index 714aa882bc..2e5ea03b71 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/info/workspace-view-content-info.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/info/workspace-view-content-info.element.ts @@ -1,14 +1,13 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; import { distinctUntilChanged } from 'rxjs'; -import type { UmbWorkspaceNodeContext } from '../../../workspace-context/workspace-node.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import type { UmbWorkspaceContentContext } from '../../workspace-content.context'; import type { DocumentDetails, MediaDetails } from '@umbraco-cms/models'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-view-content-info') -export class UmbWorkspaceViewContentInfoElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbWorkspaceViewContentInfoElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -22,7 +21,7 @@ export class UmbWorkspaceViewContentInfoElement extends UmbContextConsumerMixin( @state() private _nodeName = ''; - private _workspaceContext?: UmbWorkspaceNodeContext; + private _workspaceContext?: UmbWorkspaceContentContext; constructor() { super(); @@ -37,7 +36,7 @@ export class UmbWorkspaceViewContentInfoElement extends UmbContextConsumerMixin( private _observeContent() { if (!this._workspaceContext) return; - this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (node) => { + this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (node) => { this._nodeName = node.name as string; }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-node.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts similarity index 70% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-node.context.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts index ea22e261db..7dc25c1d41 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-node.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts @@ -1,32 +1,33 @@ import { UmbNotificationService } from '../../../../../core/notification'; import { UmbNotificationDefaultData } from '../../../../../core/notification/layouts/default'; -import { UmbWorkspaceWithStoreContext } from './workspace-with-store.context'; +import { UmbWorkspaceWithStoreContext } from '../workspace-context/workspace-with-store.context'; import { UmbNodeStoreBase } from '@umbraco-cms/stores/store'; import { ContentTreeItem } from '@umbraco-cms/backend-api'; -import { UmbContextConsumer } from '@umbraco-cms/context-api'; +import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; +import { UmbContextConsumerController } from 'src/core/context-api/consume/context-consumer.controller'; // TODO: Consider if its right to have this many class-inheritance of WorkspaceContext -export class UmbWorkspaceNodeContext< +export class UmbWorkspaceContentContext< ContentTypeType extends ContentTreeItem = ContentTreeItem, StoreType extends UmbNodeStoreBase = UmbNodeStoreBase > extends UmbWorkspaceWithStoreContext { + protected _notificationService?: UmbNotificationService; - protected _notificationConsumer!: UmbContextConsumer; public entityKey: string; public entityType: string; constructor( - target: HTMLElement, + host: UmbControllerHostInterface, defaultData: ContentTypeType, storeAlias: string, entityKey: string, entityType: string ) { - super(target, defaultData, storeAlias); + super(host, defaultData, storeAlias); - this._notificationConsumer = new UmbContextConsumer( - this._target, + new UmbContextConsumerController( + host, 'umbNotificationService', (_instance: UmbNotificationService) => { this._notificationService = _instance; @@ -37,18 +38,8 @@ export class UmbWorkspaceNodeContext< this.entityType = entityType; } - connectedCallback() { - super.connectedCallback(); - this._notificationConsumer.hostConnected(); - } - - disconnectedCallback() { - super.connectedCallback(); - this._notificationConsumer.hostDisconnected(); - } - protected _onStoreSubscription(): void { - this._dataObserver = this._store.getByKey(this.entityKey).subscribe((content) => { + this._store.getByKey(this.entityKey).subscribe((content) => { if (!content) return; // TODO: Handle nicely if there is no content data. this.update(content as any); }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.element.ts index 8764517ac7..528d0bf44c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.element.ts @@ -1,27 +1,30 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; import { distinctUntilChanged } from 'rxjs'; -import type { UmbWorkspaceNodeContext } from '../workspace-context/workspace-node.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; +import type { UmbWorkspaceContentContext } from './workspace-content.context'; import type { DocumentDetails, MediaDetails } from '@umbraco-cms/models'; -import '../workspace-entity/workspace-entity.element'; +import '../workspace-layout/workspace-layout.element'; // Lazy load // TODO: Make this dynamic, use load-extensions method to loop over extensions for this node. import './views/edit/workspace-view-content-edit.element'; import './views/info/workspace-view-content-info.element'; import type { UmbNodeStoreBase } from '@umbraco-cms/stores/store'; +import { UmbLitElement } from '@umbraco-cms/element'; type ContentTypeTypes = DocumentDetails | MediaDetails; +/** + * TODO: IMPORTANT TODO: Get rid of the content workspace. Instead we aim to get separate components that can be composed by each workspace. + * Example. Document Workspace would use a Variant-component(variant component would talk directly to the workspace-context) + * As well breadcrumbs etc. + * + */ @customElement('umb-workspace-content') -export class UmbWorkspaceContentElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbWorkspaceContentElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -72,7 +75,7 @@ export class UmbWorkspaceContentElement extends UmbContextProviderMixin( @state() _content?: ContentTypeTypes; - private _workspaceContext?: UmbWorkspaceNodeContext>; + private _workspaceContext?: UmbWorkspaceContentContext>; constructor() { super(); @@ -88,7 +91,7 @@ export class UmbWorkspaceContentElement extends UmbContextProviderMixin( private async _observeWorkspace() { if (!this._workspaceContext) return; - this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (data) => { + this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (data) => { this._content = data; }); } @@ -153,7 +156,7 @@ export class UmbWorkspaceContentElement extends UmbContextProviderMixin( render() { return html` - + - + `; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-with-store.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-with-store.context.ts index f30880719a..a24092e557 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-with-store.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-with-store.context.ts @@ -1,24 +1,24 @@ -import { Subscription } from 'rxjs'; import { UmbWorkspaceContext } from './workspace.context'; -import { UmbContextConsumer } from '@umbraco-cms/context-api'; import { UmbDataStoreBase } from '@umbraco-cms/stores/store'; import { ContentTreeItem } from '@umbraco-cms/backend-api'; +import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; +import { UmbContextConsumerController } from 'src/core/context-api/consume/context-consumer.controller'; // TODO: Consider if its right to have this many class-inheritance of WorkspaceContext export abstract class UmbWorkspaceWithStoreContext< DataType extends ContentTreeItem, StoreType extends UmbDataStoreBase > extends UmbWorkspaceContext { - protected _storeConsumer!: UmbContextConsumer; + protected _store!: StoreType; // TODO: Double check its right to assume it here, at least from a type perspective? - protected _dataObserver?: Subscription; + //protected _dataObserver?: Subscription; - constructor(target: HTMLElement, defaultData: DataType, storeAlias: string) { - super(target, defaultData); + constructor(host: UmbControllerHostInterface, defaultData: DataType, storeAlias: string) { + super(host, defaultData); // TODO: consider if store alias should be configurable of manifest: - this._storeConsumer = new UmbContextConsumer(this._target, storeAlias, (_instance: StoreType) => { + new UmbContextConsumerController(host, storeAlias, (_instance: StoreType) => { this._store = _instance; if (!this._store) { // TODO: if we keep the type assumption of _store existing, then we should here make sure to break the application in a good way. @@ -28,14 +28,6 @@ export abstract class UmbWorkspaceWithStoreContext< }); } - connectedCallback() { - this._storeConsumer.hostConnected(); - } - - disconnectedCallback() { - this._storeConsumer.hostDisconnected(); - } - protected abstract _onStoreSubscription(): void; /* { this._dataObserver = this._store.getByKey(this.entityKey).subscribe((content) => { @@ -61,6 +53,7 @@ export abstract class UmbWorkspaceWithStoreContext< } */ + /* public destroy(): void { super.destroy(); if (this._storeConsumer) { @@ -71,4 +64,5 @@ export abstract class UmbWorkspaceWithStoreContext< this._dataObserver?.unsubscribe(); } } + */ } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace.context.ts index 17de8fa414..1277e47ace 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace.context.ts @@ -1,11 +1,10 @@ import { BehaviorSubject, Observable } from "rxjs"; +import { UmbControllerHostInterface } from "src/core/controller/controller-host.mixin"; export abstract class UmbWorkspaceContext { - - protected _target!:HTMLElement; - + protected _host: UmbControllerHostInterface; // TODO: figure out how fine grained we want to make our observables. // TODO: add interface @@ -13,8 +12,8 @@ export abstract class UmbWorkspaceContext { public readonly data: Observable; - constructor(target:HTMLElement, defaultData: DataType) { - this._target = target; + constructor(host:UmbControllerHostInterface, defaultData: DataType) { + this._host = host; this._data = new BehaviorSubject(defaultData); this.data = this._data.asObservable(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity-element.interface.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity-element.interface.ts new file mode 100644 index 0000000000..c82415c97c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity-element.interface.ts @@ -0,0 +1,3 @@ +export interface UmbWorkspaceEntityElement { + set entityKey(key: string); +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity/workspace-entity.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts similarity index 76% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity/workspace-entity.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts index af179d6e83..59ca7e8672 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity/workspace-entity.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts @@ -1,32 +1,31 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { IRoutingInfo, RouterSlot } from 'router-slot'; import { map } from 'rxjs'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { createExtensionElement } from '@umbraco-cms/extensions-api'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import type { ManifestWithMeta, ManifestWorkspaceView, ManifestWorkspaceViewCollection } from '@umbraco-cms/models'; +import type { ManifestWorkspaceAction, ManifestWorkspaceView, ManifestWorkspaceViewCollection } from '@umbraco-cms/models'; import '../../body-layout/body-layout.element'; import '../../extension-slot/extension-slot.element'; +import { UmbLitElement } from '@umbraco-cms/element'; /** - * @element umb-workspace-entity + * @element umb-workspace-layout * @description - * @slot icon - Slot for rendering the entity icon - * @slot name - Slot for rendering the entity name - * @slot footer - Slot for rendering the entity footer - * @slot actions - Slot for rendering the entity actions + * @slot icon - Slot for rendering the icon + * @slot name - Slot for rendering the name + * @slot footer - Slot for rendering the workspace footer + * @slot actions - Slot for rendering the workspace actions * @slot default - slot for main content * @export - * @class UmbWorkspaceEntity - * @extends {UmbContextConsumerMixin(LitElement)} + * @class UmbWorkspaceLayout + * @extends {UmbLitElement} */ -@customElement('umb-workspace-entity') -export class UmbWorkspaceEntity extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +@customElement('umb-workspace-layout') +export class UmbWorkspaceLayout extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -56,6 +55,9 @@ export class UmbWorkspaceEntity extends UmbContextConsumerMixin(UmbObserverMixin `, ]; + @property() + public headline = ''; + /** * Alias of the workspace. The Layout will render the workspace views that are registered for this workspace alias. * @public @@ -63,9 +65,6 @@ export class UmbWorkspaceEntity extends UmbContextConsumerMixin(UmbObserverMixin * @attr * @default '' */ - @property() - public headline = ''; - @property() public alias = ''; @@ -90,12 +89,12 @@ export class UmbWorkspaceEntity extends UmbContextConsumerMixin(UmbObserverMixin } private _observeWorkspaceViews() { - this.observe( + this.observe( umbExtensionsRegistry - .extensionsOfTypes(['workspaceView', 'workspaceViewCollection']) + .extensionsOfTypes(['workspaceView', 'workspaceViewCollection']) .pipe( map((extensions) => - extensions.filter((extension) => (extension as ManifestWithMeta).meta.workspaces.includes(this.alias)) + extensions.filter((extension) => (extension).meta.workspaces.includes(this.alias)) ) ), (workspaceViews) => { @@ -106,15 +105,16 @@ export class UmbWorkspaceEntity extends UmbContextConsumerMixin(UmbObserverMixin } private async _createRoutes() { + + this._routes = []; + if (this._workspaceViews.length > 0) { - this._routes = []; this._routes = this._workspaceViews.map((view) => { return { path: `view/${view.meta.pathname}`, component: () => { if (view.type === 'workspaceViewCollection') { - console.log('!!!!!workspaceViewCollection'); return import( 'src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element' ); @@ -134,7 +134,7 @@ export class UmbWorkspaceEntity extends UmbContextConsumerMixin(UmbObserverMixin this._routes.push({ path: '**', - redirectTo: `view/${this._workspaceViews?.[0].meta.pathname}`, + redirectTo: `view/${this._workspaceViews[0].meta.pathname}`, }); this.requestUpdate(); @@ -154,7 +154,7 @@ export class UmbWorkspaceEntity extends UmbContextConsumerMixin(UmbObserverMixin private _renderTabs() { return html` - ${this._workspaceViews?.length > 0 + ${this._workspaceViews.length > 0 ? html` ${this._workspaceViews.map( @@ -187,7 +187,7 @@ export class UmbWorkspaceEntity extends UmbContextConsumerMixin(UmbObserverMixin extension.meta.workspaces.includes(this.alias)}> + .filter=${(extension: ManifestWorkspaceAction) => extension.meta.workspaces.includes(this.alias)}> `; @@ -196,6 +196,6 @@ export class UmbWorkspaceEntity extends UmbContextConsumerMixin(UmbObserverMixin declare global { interface HTMLElementTagNameMap { - 'umb-workspace-entity': UmbWorkspaceEntity; + 'umb-workspace-layout': UmbWorkspaceLayout; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity/workspace-entity.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.stories.ts similarity index 69% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity/workspace-entity.stories.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.stories.ts index 59aa40ec28..b40e13d850 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity/workspace-entity.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.stories.ts @@ -1,21 +1,21 @@ -import './workspace-entity.element'; +import './workspace-layout.element'; import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit-html'; -import type { UmbWorkspaceEntity } from './workspace-entity.element'; +import type { UmbWorkspaceLayout } from './workspace-layout.element'; export default { title: 'Workspaces/Shared/Editor Entity Layout', - component: 'umb-workspace-entity', - id: 'umb-workspace-entity', + component: 'umb-workspace-layout', + id: 'umb-workspace-layout', } as Meta; -export const AAAOverview: Story = () => html` +export const AAAOverview: Story = () => html`
Icon slot
Name slot
Footer slot
Actions slot
Default slot -
`; +`; AAAOverview.storyName = 'Overview'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/clear/property-action-clear.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/clear/property-action-clear.element.ts index 8ac1fb578b..5371832894 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/clear/property-action-clear.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/clear/property-action-clear.element.ts @@ -1,11 +1,11 @@ -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { UmbPropertyActionMenuContext } from '../shared/property-action-menu/property-action-menu.context'; import { UmbPropertyAction } from '../shared/property-action/property-action.model'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-property-action-clear') -export class UmbPropertyActionClearElement extends UmbContextConsumerMixin(LitElement) implements UmbPropertyAction { +export class UmbPropertyActionClearElement extends UmbLitElement implements UmbPropertyAction { @property() value = ''; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/copy/property-action-copy.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/copy/property-action-copy.element.ts index 9287b19726..6182985fa7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/copy/property-action-copy.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/copy/property-action-copy.element.ts @@ -1,12 +1,13 @@ -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import type { UmbNotificationDefaultData } from '../../../../core/notification/layouts/default'; import type { UmbNotificationService } from '../../../../core/notification'; import type { UmbPropertyAction } from '../shared/property-action/property-action.model'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-property-action-copy') -export class UmbPropertyActionCopyElement extends UmbContextConsumerMixin(LitElement) implements UmbPropertyAction { +export class UmbPropertyActionCopyElement extends UmbLitElement implements UmbPropertyAction { + @property() value = ''; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/shared/property-action-menu/property-action-menu.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/shared/property-action-menu/property-action-menu.element.ts index 3e3585015a..ef4caef8e7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/shared/property-action-menu/property-action-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-actions/shared/property-action-menu/property-action-menu.element.ts @@ -1,19 +1,16 @@ -import { css, CSSResultGroup, html, LitElement } from 'lit'; +import { css, CSSResultGroup, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { map } from 'rxjs'; import { UUITextStyles } from '@umbraco-ui/uui'; import { UmbPropertyActionMenuContext } from './property-action-menu.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextProviderMixin, UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { ManifestPropertyAction } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; import '../property-action/property-action.element'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-property-action-menu') -export class UmbPropertyActionMenuElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbPropertyActionMenuElement extends UmbLitElement { static styles: CSSResultGroup = [ UUITextStyles, css` @@ -68,7 +65,7 @@ export class UmbPropertyActionMenuElement extends UmbContextProviderMixin( } private _observePropertyActions() { - this.observe( + this.observe( umbExtensionsRegistry .extensionsOfType('propertyAction') .pipe( @@ -85,7 +82,7 @@ export class UmbPropertyActionMenuElement extends UmbContextProviderMixin( } private _observePropertyActionMenuOpenState() { - this.observe(this._propertyActionMenuContext.isOpen, (value) => { + this.observe(this._propertyActionMenuContext.isOpen, (value) => { this._open = value; }); } @@ -101,7 +98,7 @@ export class UmbPropertyActionMenuElement extends UmbContextProviderMixin( render() { return html` - ${this._actions?.length > 0 + ${this._actions.length > 0 ? html` (umbExtensionsRegistry.getByAlias(this.propertyEditorUIAlias), (manifest) => { + this.observe(umbExtensionsRegistry.getByAlias(this.propertyEditorUIAlias), (manifest) => { if (manifest?.type === 'propertyEditorUI') { this._observePropertyEditorModelConfig(manifest.meta.propertyEditorModel); this._propertyEditorUIConfigProperties = manifest?.meta.config?.properties || []; @@ -73,7 +72,7 @@ export class UmbPropertyEditorConfigElement extends UmbContextConsumerMixin(UmbO private _observePropertyEditorModelConfig(propertyEditorModelAlias?: string) { if (!propertyEditorModelAlias) return; - this.observe(umbExtensionsRegistry.getByAlias(propertyEditorModelAlias), (manifest) => { + this.observe(umbExtensionsRegistry.getByAlias(propertyEditorModelAlias), (manifest) => { if (manifest?.type === 'propertyEditorModel') { this._propertyEditorModelConfigProperties = manifest?.meta.config?.properties || []; this._propertyEditorModelConfigDefaultData = manifest?.meta.config?.defaultData || []; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/content-picker/property-editor-ui-content-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/content-picker/property-editor-ui-content-picker.element.ts index 6c86d999c7..7e0f41cf04 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/content-picker/property-editor-ui-content-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/content-picker/property-editor-ui-content-picker.element.ts @@ -1,16 +1,15 @@ -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import type { UmbModalService } from 'src/core/modal'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import { UmbDocumentStore } from 'src/backoffice/documents/documents/document.store'; import { FolderTreeItem } from '@umbraco-cms/backend-api'; +import { UmbLitElement } from '@umbraco-cms/element'; // TODO: rename to Document Picker @customElement('umb-property-editor-ui-content-picker') -export class UmbPropertyEditorUIContentPickerElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbPropertyEditorUIContentPickerElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -42,7 +41,7 @@ export class UmbPropertyEditorUIContentPickerElement extends UmbContextConsumerM public config = []; @state() - private _items: Array = []; + private _items?: Array; private _modalService?: UmbModalService; private _documentStore?: UmbDocumentStore; @@ -60,7 +59,7 @@ export class UmbPropertyEditorUIContentPickerElement extends UmbContextConsumerM private _observePickedDocuments() { if (!this._documentStore) return; // TODO: consider changing this to the list data endpoint when it is available - this.observe(this._documentStore.getTreeItems(this.value), (items) => { + this.observe(this._documentStore.getTreeItems(this.value), (items) => { this._items = items; }); } @@ -109,7 +108,7 @@ export class UmbPropertyEditorUIContentPickerElement extends UmbContextConsumerM } render() { - return html`${this._items.map((item) => this._renderItem(item))} + return html`${this._items?.map((item) => this._renderItem(item))} Add`; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/icon-picker/property-editor-ui-icon-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/icon-picker/property-editor-ui-icon-picker.element.ts index c90c56990b..2fb03c08e8 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/icon-picker/property-editor-ui-icon-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/icon-picker/property-editor-ui-icon-picker.element.ts @@ -1,14 +1,14 @@ -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property } from 'lit/decorators.js'; import type { UmbModalService } from 'src/core/modal'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; /** * @element umb-property-editor-ui-icon-picker */ @customElement('umb-property-editor-ui-icon-picker') -export class UmbPropertyEditorUIIconPickerElement extends UmbContextConsumerMixin(LitElement) { +export class UmbPropertyEditorUIIconPickerElement extends UmbLitElement { static styles = [UUITextStyles]; @property() diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/textarea/property-editor-ui-textarea.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/textarea/property-editor-ui-textarea.element.ts index e665c048cb..2378ec6e56 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/textarea/property-editor-ui-textarea.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/textarea/property-editor-ui-textarea.element.ts @@ -1,12 +1,11 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property } from 'lit/decorators.js'; -import { UmbControllerHostMixin } from 'src/core/controller/controller-host.mixin'; -import { UmbContextConsumerController } from 'src/core/context-api/consume/context-consumer.controller'; import { UmbWorkspacePropertyContext } from 'src/backoffice/shared/components/entity-property/workspace-property.context'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-property-editor-ui-textarea') -export class UmbPropertyEditorUITextareaElement extends UmbControllerHostMixin(LitElement) { +export class UmbPropertyEditorUITextareaElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -27,7 +26,7 @@ export class UmbPropertyEditorUITextareaElement extends UmbControllerHostMixin(L constructor() { super(); - new UmbContextConsumerController(this, 'umbPropertyContext', (instance) => { + this.consumeContext('umbPropertyContext', (instance) => { this.propertyContext = instance; }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.element.ts index 2c7a82067e..1ee69d6156 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.element.ts @@ -20,7 +20,7 @@ export class UmbWorkspaceDictionaryElement extends LitElement { render() { return html` - Dictionary Workspace + Dictionary Workspace `; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user-header-app.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user-header-app.element.ts index e38bab22e2..2cbc66df2c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user-header-app.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user-header-app.element.ts @@ -1,14 +1,13 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, CSSResultGroup, html, LitElement } from 'lit'; +import { css, CSSResultGroup, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbCurrentUserStore } from './current-user.store'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import type { UserDetails } from '@umbraco-cms/models'; import { UmbModalService } from 'src/core/modal'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-current-user-header-app') -export class UmbCurrentUserHeaderApp extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbCurrentUserHeaderApp extends UmbLitElement { static styles: CSSResultGroup = [ UUITextStyles, css` @@ -36,7 +35,7 @@ export class UmbCurrentUserHeaderApp extends UmbContextConsumerMixin(UmbObserver private async _observeCurrentUser() { if (!this._currentUserStore) return; - this.observe(this._currentUserStore.currentUser, (currentUser) => { + this.observe(this._currentUserStore.currentUser, (currentUser) => { this._currentUser = currentUser; }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-dashboard-test.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-dashboard-test.element.ts index ede0abf1ff..e2db3b031e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-dashboard-test.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-dashboard-test.element.ts @@ -1,13 +1,10 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement } from 'lit/decorators.js'; -import { UmbContextProviderMixin, UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-user-dashboard-test') -export class UmbUserDashboardTestElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbUserDashboardTestElement extends UmbLitElement { static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/actions/workspace-action-user-group-save.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/actions/workspace-action-user-group-save.element.ts index 039f1458e8..6aa5d8f09a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/actions/workspace-action-user-group-save.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/actions/workspace-action-user-group-save.element.ts @@ -1,12 +1,12 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import type { UUIButtonState } from '@umbraco-ui/uui'; import type { UmbWorkspaceUserContext } from '../../../users/workspace/user-workspace.context'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-action-user-group-save') -export class UmbWorkspaceActionUserGroupSaveElement extends UmbContextConsumerMixin(LitElement) { +export class UmbWorkspaceActionUserGroupSaveElement extends UmbLitElement { static styles = [UUITextStyles, css``]; @state() diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts index 5dac927b17..6a796cd2ba 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts @@ -1,5 +1,6 @@ -import { UmbWorkspaceNodeContext } from '../../../shared/components/workspace/workspace-context/workspace-node.context'; +import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; import type { UmbUserGroupStore, UmbUserGroupStoreItemType } from 'src/backoffice/users/user-groups/user-group.store'; +import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; const DefaultDataTypeData = { key: '', @@ -13,11 +14,11 @@ const DefaultDataTypeData = { users: [], } as UmbUserGroupStoreItemType; -export class UmbWorkspaceUserGroupContext extends UmbWorkspaceNodeContext< +export class UmbWorkspaceUserGroupContext extends UmbWorkspaceContentContext< UmbUserGroupStoreItemType, UmbUserGroupStore > { - constructor(target: HTMLElement, entityKey: string) { - super(target, DefaultDataTypeData, 'umbUserStore', entityKey, 'userGroup'); + constructor(host: UmbControllerHostInterface, entityKey: string) { + super(host, DefaultDataTypeData, 'umbUserStore', entityKey, 'userGroup'); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.element.ts index ddbae4e153..c5c1a7fbe7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.element.ts @@ -1,23 +1,22 @@ import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { distinctUntilChanged } from 'rxjs'; import { UmbWorkspaceUserGroupContext } from './user-group-workspace.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; -import type { ManifestWorkspaceAction, UserDetails, UserGroupDetails } from '@umbraco-cms/models'; +import type { ManifestWorkspaceAction, UserGroupDetails } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; -import { UmbUserStore } from 'src/backoffice/users/users/user.store'; +import type { UmbUserStore } from 'src/backoffice/users/users/user.store'; import 'src/auth/components/input-user/input-user.element'; import 'src/backoffice/shared/components/input-section/input-section.element'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { UmbWorkspaceEntityElement } from 'src/backoffice/shared/components/workspace/workspace-entity-element.interface'; @customElement('umb-user-group-workspace') -export class UmbUserGroupWorkspaceElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbUserGroupWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement { + static styles = [ UUITextStyles, css` @@ -217,17 +216,6 @@ export class UmbUserGroupWorkspaceElement extends UmbContextProviderMixin( }); } - connectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._workspaceContext?.connectedCallback(); - } - disconnectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._workspaceContext?.disconnectedCallback(); - } - protected _provideWorkspace() { if (this._entityKey) { this._workspaceContext = new UmbWorkspaceUserGroupContext(this, this._entityKey); @@ -262,8 +250,7 @@ export class UmbUserGroupWorkspaceElement extends UmbContextProviderMixin( private _observeUserGroup() { if (!this._workspaceContext) return; - this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (userGroup) => { - if (!this._userGroup) return; + this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (userGroup) => { this._userGroup = userGroup; }); } @@ -273,7 +260,8 @@ export class UmbUserGroupWorkspaceElement extends UmbContextProviderMixin( // TODO: Create method to only get users from this userGroup // TODO: Find a better way to only call this once at the start - this.observe(this._userStore.getAll(), (users: Array) => { + this.observe(this._userStore.getAll(), (users) => { + // TODO: handle if there is no users. if (!this._userKeys && users.length > 0) { this._userKeys = users.filter((user) => user.userGroups.includes(this.entityKey)).map((user) => user.key); this._updateProperty('users', this._userKeys); @@ -399,13 +387,13 @@ export class UmbUserGroupWorkspaceElement extends UmbContextProviderMixin( if (!this._userGroup) return nothing; return html` - +
${this.renderLeftColumn()}
${this.renderRightColumn()}
-
+ `; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/user-group-table-sections-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/user-group-table-sections-column-layout.element.ts index c2ec9218ec..905a72a6aa 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/user-group-table-sections-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/user-group-table-sections-column-layout.element.ts @@ -1,12 +1,11 @@ -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; -import type { ManifestSection } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { UmbTableItem } from 'src/backoffice/shared/components/table'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-user-group-table-sections-column-layout') -export class UmbUserGroupTableSectionsColumnLayoutElement extends UmbObserverMixin(LitElement) { +export class UmbUserGroupTableSectionsColumnLayoutElement extends UmbLitElement { @property({ type: Object, attribute: false }) item!: UmbTableItem; @@ -23,7 +22,7 @@ export class UmbUserGroupTableSectionsColumnLayoutElement extends UmbObserverMix } private observeSectionNames() { - this.observe>(umbExtensionsRegistry.extensionsOfType('section'), (sections) => { + this.observe(umbExtensionsRegistry.extensionsOfType('section'), (sections) => { this._sectionsNames = sections.filter((x) => this.value.includes(x.alias)).map((x) => x.meta.label || x.name); }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/workspace-view-user-groups.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/workspace-view-user-groups.element.ts index 22cb493556..7fc29ade07 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/workspace-view-user-groups.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/workspace-view-user-groups.element.ts @@ -1,5 +1,5 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbTableColumn, @@ -10,16 +10,15 @@ import { UmbTableOrderedEvent, UmbTableSelectedEvent, } from 'src/backoffice/shared/components/table'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { UserGroupDetails } from '@umbraco-cms/models'; import './user-group-table-name-column-layout.element'; import './user-group-table-sections-column-layout.element'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { UmbUserGroupStore } from 'src/backoffice/users/user-groups/user-group.store'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-view-user-groups') -export class UmbWorkspaceViewUserGroupsElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbWorkspaceViewUserGroupsElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -81,7 +80,7 @@ export class UmbWorkspaceViewUserGroupsElement extends UmbContextConsumerMixin(U private _observeUserGroups() { if (!this._userGroupStore) return; - this.observe(this._userGroupStore.getAll(), (userGroups) => { + this.observe(this._userGroupStore.getAll(), (userGroups) => { this._userGroups = userGroups; this._createTableItems(this._userGroups); }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.element.ts index 9d0ca63de6..3bbe72aadc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.element.ts @@ -1,17 +1,16 @@ -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import type { UmbSectionViewUsersElement } from '../../section-view-users.element'; import { getTagLookAndColor } from '../../../../../../../auth/utils'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { UserDetails, UserEntity, UserGroupDetails, UserGroupEntity } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { UmbUserGroupStore } from 'src/backoffice/users/user-groups/user-group.store'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-view-users-grid') -export class UmbWorkspaceViewUsersGridElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbWorkspaceViewUsersGridElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -66,14 +65,14 @@ export class UmbWorkspaceViewUsersGridElement extends UmbContextConsumerMixin(Um private _observeUsers() { if (!this._usersContext) return; - this.observe>(this._usersContext.users, (users) => { + this.observe(this._usersContext.users, (users) => { this._users = users; }); } private _observeUserGroups() { if (!this._userGroupStore) return; - this.observe>( + this.observe( this._userGroupStore.getAll(), (userGroups) => (this._userGroups = userGroups) ); @@ -81,7 +80,7 @@ export class UmbWorkspaceViewUsersGridElement extends UmbContextConsumerMixin(Um private _observeSelection() { if (!this._usersContext) return; - this.observe>(this._usersContext.selection, (selection) => (this._selection = selection)); + this.observe(this._usersContext.selection, (selection) => (this._selection = selection)); } private _isSelected(key: string) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/workspace-view-users-table.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/workspace-view-users-table.element.ts index 8f5d69d1ae..b1529a7035 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/workspace-view-users-table.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/workspace-view-users-table.element.ts @@ -1,4 +1,4 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; import type { UmbSectionViewUsersElement } from '../../section-view-users.element'; @@ -11,16 +11,15 @@ import { UmbTableConfig, UmbTableOrderedEvent, } from '../../../../../../shared/components/table/table.element'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import type { UserDetails, UserGroupDetails, UserGroupEntity } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import type { UserDetails, UserGroupEntity } from '@umbraco-cms/models'; import './column-layouts/name/user-table-name-column-layout.element'; import './column-layouts/status/user-table-status-column-layout.element'; import { UmbUserGroupStore } from 'src/backoffice/users/user-groups/user-group.store'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-view-users-table') -export class UmbWorkspaceViewUsersTableElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbWorkspaceViewUsersTableElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -88,7 +87,7 @@ export class UmbWorkspaceViewUsersTableElement extends UmbContextConsumerMixin(U private _observeUsers() { if (!this._usersContext) return; - this.observe>(this._usersContext.users, (users) => { + this.observe(this._usersContext.users, (users) => { this._users = users; this._createTableItems(this._users); }); @@ -104,7 +103,7 @@ export class UmbWorkspaceViewUsersTableElement extends UmbContextConsumerMixin(U private _observeUserGroups() { if (!this._userGroupStore) return; - this.observe>(this._userGroupStore.getAll(), (userGroups) => { + this.observe(this._userGroupStore.getAll(), (userGroups) => { this._userGroups = userGroups; this._createTableItems(this._users); }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.element.ts index 4796e6506c..49813a2501 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.element.ts @@ -1,24 +1,21 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; import { BehaviorSubject, Observable } from 'rxjs'; import type { IRoute, IRoutingInfo } from 'router-slot'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; -import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api'; import './list-view-layouts/table/workspace-view-users-table.element'; import './list-view-layouts/grid/workspace-view-users-grid.element'; import './workspace-view-users-selection.element'; import './workspace-view-users-invite.element'; import type { ManifestWorkspace, UserDetails } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { UmbUserStore } from 'src/backoffice/users/users/user.store'; import { createExtensionElement } from '@umbraco-cms/extensions-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-section-view-users') -export class UmbSectionViewUsersElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbSectionViewUsersElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -51,9 +48,10 @@ export class UmbSectionViewUsersElement extends UmbContextProviderMixin( this._observeUsers(); }); // TODO: consider this context name, is it to broad? + // TODO: Stop using it self as a context api. this.provideContext('umbUsersContext', this); - this.observe(umbExtensionsRegistry?.extensionsOfType('workspace'), (workspaceExtensions) => { + this.observe(umbExtensionsRegistry?.extensionsOfType('workspace'), (workspaceExtensions) => { this._workspaces = workspaceExtensions; this._createRoutes(); }); @@ -95,11 +93,11 @@ export class UmbSectionViewUsersElement extends UmbContextProviderMixin( if (!this._userStore) return; if (this._search.getValue()) { - this.observe>(this._userStore.getByName(this._search.getValue()), (users) => + this.observe(this._userStore.getByName(this._search.getValue()), (users) => this._users.next(users) ); } else { - this.observe>(this._userStore.getAll(), (users) => this._users.next(users)); + this.observe(this._userStore.getAll(), (users) => this._users.next(users)); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-create.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-create.element.ts index 5f9f327894..173df0fc7a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-create.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-create.element.ts @@ -3,7 +3,6 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, query, state } from 'lit/decorators.js'; import { UUIInputPasswordElement } from '@umbraco-ui/uui'; import { UmbInputPickerUserGroupElement } from 'src/auth/components/input-user-group/input-user-group.element'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { UserDetails } from '@umbraco-cms/models'; import { UmbNotificationService } from 'src/core/notification'; import { UmbNotificationDefaultData } from 'src/core/notification/layouts/default'; @@ -12,7 +11,7 @@ import { UmbUserStore } from 'src/backoffice/users/users/user.store'; export type UsersViewType = 'list' | 'grid'; @customElement('umb-workspace-view-users-create') -export class UmbWorkspaceViewUsersCreateElement extends UmbContextConsumerMixin(UmbModalLayoutElement) { +export class UmbWorkspaceViewUsersCreateElement extends UmbModalLayoutElement { static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-invite.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-invite.element.ts index 88b08bb495..5c64294fec 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-invite.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-invite.element.ts @@ -2,14 +2,13 @@ import { css, html, nothing } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, query, state } from 'lit/decorators.js'; import { UmbInputPickerUserGroupElement } from 'src/auth/components/input-user-group/input-user-group.element'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { UserDetails } from '@umbraco-cms/models'; import { UmbModalLayoutElement } from 'src/core/modal'; import { UmbUserStore } from 'src/backoffice/users/users/user.store'; export type UsersViewType = 'list' | 'grid'; @customElement('umb-workspace-view-users-invite') -export class UmbWorkspaceViewUsersInviteElement extends UmbContextConsumerMixin(UmbModalLayoutElement) { +export class UmbWorkspaceViewUsersInviteElement extends UmbModalLayoutElement { static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.element.ts index 42ae6d032a..cfc5518a70 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.element.ts @@ -1,12 +1,11 @@ -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; import { IRoute } from 'router-slot'; import { UUIPopoverElement } from '@umbraco-ui/uui'; import type { UmbSectionViewUsersElement } from './section-view-users.element'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import { UmbLitElement } from '@umbraco-cms/element'; import { UmbModalService } from 'src/core/modal'; import './list-view-layouts/table/workspace-view-users-table.element'; @@ -17,7 +16,7 @@ import './workspace-view-users-create.element'; export type UsersViewType = 'list' | 'grid'; @customElement('umb-workspace-view-users-overview') -export class UmbWorkspaceViewUsersOverviewElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbWorkspaceViewUsersOverviewElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -122,7 +121,7 @@ export class UmbWorkspaceViewUsersOverviewElement extends UmbContextConsumerMixi private _observeSelection() { if (!this._usersContext) return; - this.observe>(this._usersContext.selection, (selection) => (this._selection = selection)); + this.observe(this._usersContext.selection, (selection) => (this._selection = selection)); } private _toggleViewType() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.element.ts index dfe5d5a090..3338fc6984 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.element.ts @@ -1,13 +1,12 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; import { UmbSectionViewUsersElement } from './section-view-users.element'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { UmbUserStore } from 'src/backoffice/users/users/user.store'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-view-users-selection') -export class UmbWorkspaceViewUsersSelectionElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbWorkspaceViewUsersSelectionElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -48,12 +47,12 @@ export class UmbWorkspaceViewUsersSelectionElement extends UmbContextConsumerMix private _observeSelection() { if (!this._usersContext) return; - this.observe>(this._usersContext.selection, (selection) => (this._selection = selection)); + this.observe(this._usersContext.selection, (selection) => (this._selection = selection)); } private _observeTotalUsers() { if (!this._userStore) return; - this.observe(this._userStore.totalUsers, (totalUsers) => (this._totalUsers = totalUsers)); + this.observe(this._userStore.totalUsers, (totalUsers) => (this._totalUsers = totalUsers)); } private _handleClearSelection() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/workspace-action-user-save.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/workspace-action-user-save.element.ts index 8f6434266e..43370e5c02 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/workspace-action-user-save.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/workspace-action-user-save.element.ts @@ -1,12 +1,12 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import type { UUIButtonState } from '@umbraco-ui/uui'; import { UmbWorkspaceUserContext } from '../user-workspace.context'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-workspace-action-user-save') -export class UmbWorkspaceActionUserSaveElement extends UmbContextConsumerMixin(LitElement) { +export class UmbWorkspaceActionUserSaveElement extends UmbLitElement { static styles = [UUITextStyles, css``]; @state() diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts index 817943968d..4d534f72a4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts @@ -1,5 +1,6 @@ -import { UmbWorkspaceNodeContext } from '../../../shared/components/workspace/workspace-context/workspace-node.context'; +import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; import type { UmbUserStore, UmbUserStoreItemType } from 'src/backoffice/users/users/user.store'; +import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; const DefaultDataTypeData = { key: '', @@ -19,8 +20,8 @@ const DefaultDataTypeData = { mediaStartNodes: [], } as UmbUserStoreItemType; -export class UmbWorkspaceUserContext extends UmbWorkspaceNodeContext { - constructor(target: HTMLElement, entityKey: string) { - super(target, DefaultDataTypeData, 'umbUserStore', entityKey, 'user'); +export class UmbWorkspaceUserContext extends UmbWorkspaceContentContext { + constructor(host: UmbControllerHostInterface, entityKey: string) { + super(host, DefaultDataTypeData, 'umbUserStore', entityKey, 'user'); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts index 6ec5c59d31..1f1a9b60e4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts @@ -1,5 +1,5 @@ import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; -import { css, html, LitElement, nothing, TemplateResult } from 'lit'; +import { css, html, nothing, TemplateResult } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; @@ -10,20 +10,18 @@ import { getTagLookAndColor } from '../../../../auth/utils'; import { UmbCurrentUserStore } from '../../current-user/current-user.store'; import { UmbWorkspaceUserContext } from './user-workspace.context'; -import { UmbContextProviderMixin, UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { UserDetails } from '@umbraco-cms/models'; import { UmbModalService } from 'src/core/modal'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import 'src/auth/components/input-user-group/input-user-group.element'; import '../../../shared/property-editors/uis/content-picker/property-editor-ui-content-picker.element'; -import '../../../shared/components/workspace/workspace-entity/workspace-entity.element'; +import '../../../shared/components/workspace/workspace-layout/workspace-layout.element'; +import { UmbLitElement } from '@umbraco-cms/element'; +import type { UmbWorkspaceEntityElement } from 'src/backoffice/shared/components/workspace/workspace-entity-element.interface'; @customElement('umb-user-workspace') -export class UmbUserWorkspaceElement extends UmbContextProviderMixin( - UmbContextConsumerMixin(UmbObserverMixin(LitElement)) -) { +export class UmbUserWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement{ static styles = [ UUITextStyles, css` @@ -84,7 +82,7 @@ export class UmbUserWorkspaceElement extends UmbContextProviderMixin( ]; @state() - private _currentUser?: UserDetails | null; + private _currentUser?: UserDetails; private _currentUserStore?: UmbCurrentUserStore; private _modalService?: UmbModalService; @@ -104,7 +102,7 @@ export class UmbUserWorkspaceElement extends UmbContextProviderMixin( private _workspaceContext?: UmbWorkspaceUserContext; @state() - private _user?: UserDetails | null; + private _user?: UserDetails; @state() private _userName = ''; @@ -118,17 +116,6 @@ export class UmbUserWorkspaceElement extends UmbContextProviderMixin( }); } - connectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._workspaceContext?.connectedCallback(); - } - disconnectedCallback(): void { - super.connectedCallback(); - // TODO: avoid this connection, our own approach on Lit-Controller could be handling this case. - this._workspaceContext?.disconnectedCallback(); - } - protected _provideWorkspace() { if (this._entityKey) { this._workspaceContext = new UmbWorkspaceUserContext(this, this._entityKey); @@ -141,7 +128,7 @@ export class UmbUserWorkspaceElement extends UmbContextProviderMixin( if (!this._currentUserStore) return; // TODO: do not have static current user service, we need to make a ContextAPI for this. - this.observe(this._currentUserStore.currentUser, (currentUser) => { + this.observe(this._currentUserStore.currentUser, (currentUser) => { this._currentUser = currentUser; }); } @@ -149,8 +136,7 @@ export class UmbUserWorkspaceElement extends UmbContextProviderMixin( private async _observeUser() { if (!this._workspaceContext) return; - this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (user) => { - if (!user) return; + this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (user) => { this._user = user; if (user.name !== this._userName) { this._userName = user.name; @@ -362,13 +348,13 @@ export class UmbUserWorkspaceElement extends UmbContextProviderMixin( if (!this._user) return html`User not found`; return html` - +
${this._renderLeftColumn()}
${this._renderRightColumn()}
-
+ `; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.controller.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.controller.ts index 53c51dd666..8b02a0a925 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.controller.ts @@ -4,11 +4,17 @@ import type { UmbControllerInterface } from 'src/core/controller/controller.inte import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; -export class UmbContextConsumerController extends UmbContextConsumer implements UmbControllerInterface { +export class UmbContextConsumerController extends UmbContextConsumer implements UmbControllerInterface { constructor(host:UmbControllerHostInterface, contextAlias: string, callback: UmbContextCallback) { super(host, contextAlias, callback); host.addController(this); } + public destroy() { + if (this.host) { + this.host.removeController(this); + } + } + } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.mixin.ts deleted file mode 100644 index 63e44ead40..0000000000 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.mixin.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { HTMLElementConstructor } from '../../models'; -import { UmbContextConsumer } from './context-consumer'; - -export declare class UmbContextConsumerInterface { - consumeContext(alias: string, callback: (_instance: any) => void): void; - consumeAllContexts(contextAliases: string[], callback: (_instances: ResolvedContexts) => void): void; - whenAvailableOrChanged(contextAliases: string[], callback?: () => void): void; -} - -// TODO: can we use this aliases to generate the key of this type -interface ResolvedContexts { - [key: string]: any; -} - -/** - * This mixin enables the component to consume contexts. - * This is done by calling the `consumeContext` method. - * - * @param {Object} superClass - superclass to be extended. - * @mixin - */ -export const UmbContextConsumerMixin = (superClass: T) => { - class UmbContextConsumerClass extends superClass { - // all context requesters in the element - _consumers: Map = new Map(); - // all successfully resolved context requests - _resolved: Map = new Map(); - - _attached = false; - - /** - * Setup a subscription for a context. The callback is called when the context is resolved. - * @param {string} alias - * @param {method} callback Callback method called when context is resolved. - */ - consumeContext(alias: string, callback: (_instance: any) => void): void { - this._createContextConsumers([alias], (resolvedContexts) => { - callback(resolvedContexts[alias]); - }); - } - - /** - * 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. - */ - consumeAllContexts(_contextAliases: Array, callback: (_instances: ResolvedContexts) => void) { - this._createContextConsumers(_contextAliases, (resolvedContexts) => { - callback?.(resolvedContexts); - }); - } - - private _createContextConsumers(aliases: Array, resolvedCallback: (_instances: ResolvedContexts) => void) { - aliases.forEach((alias) => { - const consumer = new UmbContextConsumer(this, alias, (_instance: any) => { - this._resolved.set(alias, _instance); - - const result: ResolvedContexts = {}; - - //check if all contexts are resolved - const resolvedContexts = aliases.map((alias) => (result[alias] = this._resolved.get(alias))); - const allResolved = resolvedContexts.every((context) => context !== undefined); - - if (allResolved) { - resolvedCallback(result); - } - }); - - if (this._consumers.has(alias)) { - const consumers = this._consumers.get(alias); - consumers?.push(consumer); - } else { - this._consumers.set(alias, [consumer]); - } - - if (this._attached) { - consumer.hostConnected(); - } - }); - } - - // TODO: remove requester.. - - connectedCallback() { - super.connectedCallback?.(); - this._attached = true; - this._consumers.forEach((consumers) => consumers.forEach((consumer) => consumer.hostConnected())); - } - - disconnectedCallback() { - super.disconnectedCallback?.(); - this._attached = false; - this._consumers.forEach((consumers) => consumers.forEach((consumer) => consumer.hostDisconnected())); - this._resolved.clear(); - } - - // might return a object, so you can unsubscribe. - whenAvailableOrChanged(_contextAliases: string[]) { - // TODO: To be done. - } - } - - return UmbContextConsumerClass as unknown as HTMLElementConstructor & T; -}; - -declare global { - interface HTMLElement { - connectedCallback(): void; - disconnectedCallback(): void; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.ts index 45ace142f1..84c8ba1af4 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.ts @@ -5,22 +5,38 @@ import { UmbContextRequestEventImplementation, UmbContextCallback } from './cont * @export * @class UmbContextConsumer */ -export class UmbContextConsumer { +export class UmbContextConsumer { + + private _instance?: unknown; + get instance(): unknown | undefined { + return this._instance; + } + + get consumerAlias() { + return this._contextAlias; + } + /** * Creates an instance of UmbContextConsumer. - * @param {EventTarget} target + * @param {EventTarget} host * @param {string} _contextAlias * @param {UmbContextCallback} _callback * @memberof UmbContextConsumer */ - constructor(protected target: EventTarget, private _contextAlias: string, private _callback: UmbContextCallback) {} + constructor(protected host: HostType, private _contextAlias: string, private _callback: UmbContextCallback) {} + + + private _onResponse = (instance: unknown) => { + this._instance = instance; + this._callback(instance); + } /** * @memberof UmbContextConsumer */ public request() { - const event = new UmbContextRequestEventImplementation(this._contextAlias, this._callback); - this.target.dispatchEvent(event); + const event = new UmbContextRequestEventImplementation(this._contextAlias, this._onResponse); + this.host.dispatchEvent(event); } public hostConnected() { diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/is-context-consumer-type.function.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/is-context-consumer-type.function.ts new file mode 100644 index 0000000000..f6339ee4f6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/is-context-consumer-type.function.ts @@ -0,0 +1,7 @@ +import { UmbContextConsumer } from './context-consumer'; + +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/core/context-api/index.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/index.ts index ad3d268ecb..c9bc27c68a 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/index.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/index.ts @@ -1,6 +1,4 @@ export * from './consume/context-consumer'; -export * from './consume/context-consumer.mixin'; export * from './consume/context-request.event'; export * from './provide/context-provider'; -export * from './provide/context-provider.mixin'; export * from './provide/context-provide.event'; diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.controller.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.controller.ts index 5f95f76ac1..10bce6f5f0 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.controller.ts @@ -3,11 +3,21 @@ import type { UmbControllerInterface } from 'src/core/controller/controller.inte import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; -export class UmbContextProviderController extends UmbContextProvider implements UmbControllerInterface { +export class UmbContextProviderController extends UmbContextProvider implements UmbControllerInterface { constructor(host:UmbControllerHostInterface, contextAlias: string, instance: unknown) { super(host, contextAlias, instance); + + // TODO: What if this API is already provided with this alias? maybe handle this in the controller: + // TODO: Remove/destroy existing controller of same alias. + host.addController(this); } + public destroy() { + if (this.host) { + this.host.removeController(this); + } + } + } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.element.test.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.element.test.ts index 2eb5aabcc4..a200c4ebf5 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.element.test.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.element.test.ts @@ -1,12 +1,10 @@ import { expect, fixture, html } from '@open-wc/testing'; -import { LitElement } from 'lit'; import { customElement } from 'lit/decorators.js'; - -import { UmbContextConsumerMixin } from '../consume/context-consumer.mixin'; import { UmbContextProviderElement } from './context-provider.element'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-context-test') -export class ContextTestElement extends UmbContextConsumerMixin(LitElement) { +export class ContextTestElement extends UmbLitElement { public value: string | null = null; constructor() { super(); diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.element.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.element.ts index 575e81f818..cf8d3da2c1 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.element.ts @@ -1,10 +1,9 @@ -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; - -import { UmbContextProviderMixin } from './context-provider.mixin'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-context-provider') -export class UmbContextProviderElement extends UmbContextProviderMixin(LitElement) { +export class UmbContextProviderElement extends UmbLitElement { /** * The value to provide to the context. * @required diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.mixin.test.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.mixin.test.ts deleted file mode 100644 index c68277e517..0000000000 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.mixin.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -import { UmbContextProvider } from './context-provider'; -import { UmbContextProviderMixin } from './context-provider.mixin'; - -class MyClass { - prop: string; - - constructor(text: string) { - this.prop = text; - } -} - -class MyTestProviderElement extends UmbContextProviderMixin(HTMLElement) { - constructor() { - super(); - this.provideContext('my-test-context-1', new MyClass('context value 1')); - this.provideContext('my-test-context-2', new MyClass('context value 2')); - } -} - -customElements.define('my-test-provider-element', MyTestProviderElement); - -describe('UmbContextProviderMixin', async () => { - let element: MyTestProviderElement; - let _providers: Map; - - beforeEach(async () => { - element = await fixture(html``); - _providers = (element as any)['_providers']; - }); - - it('sets all providers to element', () => { - expect(_providers.has('my-test-context-1')).to.be.true; - expect(_providers.has('my-test-context-2')).to.be.true; - }); - - it('can not set context with same key as already existing context', () => { - const provider = _providers.get('my-test-context-1'); - expect(provider).to.not.be.undefined; - if (!provider) return; - expect((provider['_instance'] as MyClass).prop).to.eq('context value 1'); - element.provideContext('my-test-context-1', new MyClass('new context value 1')); - expect((provider['_instance'] as MyClass).prop).to.eq('context value 1'); - }); -}); diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.mixin.ts deleted file mode 100644 index 8647039eb0..0000000000 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.mixin.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { HTMLElementConstructor } from '../../models'; -import { UmbContextProvider } from './context-provider'; - -export declare class UmbContextProviderMixinInterface { - provideContext(alias: string, instance: unknown): void; -} - -export const UmbContextProviderMixin = (superClass: T) => { - class UmbContextProviderClass extends superClass { - _providers: Map = new Map(); - - _attached = false; - - provideContext(alias: string, instance: unknown) { - // TODO: Consider if its right to re-publish the context? - const existingProvider = this._providers.get(alias); - if (existingProvider) { - existingProvider.hostDisconnected(); - } - - const provider = new UmbContextProvider(this, alias, instance); - this._providers.set(alias, provider); - // TODO: if already connected then attach the new one. - if (this._attached) { - provider.hostConnected(); - } - } - - // TODO: unprovide method to enforce a detach? - - connectedCallback() { - super.connectedCallback?.(); - this._attached = true; - this._providers.forEach((provider) => provider.hostConnected()); - } - - disconnectedCallback() { - super.disconnectedCallback?.(); - this._attached = false; - this._providers.forEach((provider) => provider.hostDisconnected()); - } - } - - return UmbContextProviderClass as unknown as HTMLElementConstructor & T; -}; - -declare global { - interface HTMLElement { - connectedCallback(): void; - disconnectedCallback(): void; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.ts index f07c2c3551..3bb525cd56 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.ts @@ -5,8 +5,10 @@ import { UmbContextProvideEventImplementation } from './context-provide.event'; * @export * @class UmbContextProvider */ -export class UmbContextProvider { - protected host: EventTarget; +export class UmbContextProvider { + + protected host: HostType; + private _contextAlias: string; private _instance: unknown; @@ -17,7 +19,7 @@ export class UmbContextProvider { * @param {*} instance * @memberof UmbContextProvider */ - constructor(host: EventTarget, contextAlias: string, instance: unknown) { + constructor(host: HostType, contextAlias: string, instance: unknown) { this.host = host; this._contextAlias = contextAlias; this._instance = instance; diff --git a/src/Umbraco.Web.UI.Client/src/core/controller/controller-host.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/controller/controller-host.mixin.ts index 51a6c648a9..7d6f0d7ec6 100644 --- a/src/Umbraco.Web.UI.Client/src/core/controller/controller-host.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/core/controller/controller-host.mixin.ts @@ -4,6 +4,8 @@ import { UmbControllerInterface } from './controller.interface'; export declare class UmbControllerHostInterface extends HTMLElement { //#controllers:UmbController[]; //#attached:boolean; + hasController(controller:UmbControllerInterface): boolean; + getControllers(filterMethod: (ctrl: UmbControllerInterface) => boolean): UmbControllerInterface[]; addController(controller:UmbControllerInterface): void; removeController(controller:UmbControllerInterface): void; } @@ -22,6 +24,22 @@ export const UmbControllerHostMixin = (superCl #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 @@ -44,6 +62,7 @@ export const UmbControllerHostMixin = (superCl if(this.#attached) { ctrl.hostDisconnected(); } + ctrl.destroy(); } } diff --git a/src/Umbraco.Web.UI.Client/src/core/controller/controller.class.ts b/src/Umbraco.Web.UI.Client/src/core/controller/controller.class.ts index 7f9603e0bf..355bee9e47 100644 --- a/src/Umbraco.Web.UI.Client/src/core/controller/controller.class.ts +++ b/src/Umbraco.Web.UI.Client/src/core/controller/controller.class.ts @@ -2,20 +2,20 @@ import { UmbControllerHostInterface } from './controller-host.mixin'; import { UmbControllerInterface } from './controller.interface'; export abstract class UmbController implements UmbControllerInterface { - protected _host?: UmbControllerHostInterface; + protected host?: UmbControllerHostInterface; constructor(host: UmbControllerHostInterface) { - this._host = host; - this._host.addController(this); + this.host = host; + 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; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/controller/controller.interface.ts b/src/Umbraco.Web.UI.Client/src/core/controller/controller.interface.ts index 65c795aed4..a3dadfb0cb 100644 --- a/src/Umbraco.Web.UI.Client/src/core/controller/controller.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/core/controller/controller.interface.ts @@ -1,4 +1,5 @@ export interface UmbControllerInterface { hostConnected(): void; hostDisconnected(): void; + destroy(): void; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/element/element.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/element/element.mixin.ts new file mode 100644 index 0000000000..73bbeea806 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/element/element.mixin.ts @@ -0,0 +1,88 @@ +import { Observable } from 'rxjs'; +import { UmbContextConsumerController } from '../context-api/consume/context-consumer.controller'; +import { UmbContextCallback } from '../context-api/consume/context-request.event'; +import { UmbContextProviderController } from '../context-api/provide/context-provider.controller'; +import { UmbControllerHostInterface, UmbControllerHostMixin } from '../controller/controller-host.mixin'; +import type { HTMLElementConstructor } from '../models'; +import { UmbObserverController } from '../observable-api/observer.controller'; + +// TODO: can we use this aliases to generate the key of this type +interface ResolvedContexts { + [key: string]: any; +} + +export declare class UmbElementMixinInterface extends UmbControllerHostInterface { + observe(source: Observable, callback: (_value: T) => void): UmbObserverController; + provideContext(alias: string, instance: unknown): UmbContextProviderController; + consumeContext(alias: string, callback: UmbContextCallback): UmbContextConsumerController; + consumeAllContexts(contextAliases: string[], callback: (_instances: ResolvedContexts) => void): void; +} + +export const UmbElementMixin = (superClass: T) => { + class UmbElementMixinClass extends UmbControllerHostMixin(superClass) { + + /** + * @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, callback: (_value: T) => void): UmbObserverController { + return new UmbObserverController(this, source, callback); + } + + /** + * @description Provide a context API for this or child elements. + * @param {string} alias + * @param {instance} instance The API instance to be exposed. + * @return {UmbContextProviderController} Reference to a Context Provider Controller instance + * @memberof UmbElementMixin + */ + provideContext(alias: string, instance: unknown): UmbContextProviderController { + return new UmbContextProviderController(this, alias, instance); + } + + /** + * @description Setup a subscription for a context. The callback is called when the context is resolved. + * @param {string} alias + * @param {method} callback Callback method called when context is resolved. + * @return {UmbContextConsumerController} Reference to a Context Consumer Controller instance + * @memberof UmbElementMixin + */ + consumeContext(alias: string, callback: UmbContextCallback): 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 + */ + 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] = contextCtrl.instance; + }); + + callback(result); + } + }) + ); + } + + + } + + return UmbElementMixinClass as unknown as HTMLElementConstructor & T; +}; diff --git a/src/Umbraco.Web.UI.Client/src/core/element/index.ts b/src/Umbraco.Web.UI.Client/src/core/element/index.ts new file mode 100644 index 0000000000..4048b79c02 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/element/index.ts @@ -0,0 +1,2 @@ +export * from './element.mixin'; +export * from './lit-element.element'; diff --git a/src/Umbraco.Web.UI.Client/src/core/element/lit-element.element.ts b/src/Umbraco.Web.UI.Client/src/core/element/lit-element.element.ts new file mode 100644 index 0000000000..3e8bd6ea47 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/element/lit-element.element.ts @@ -0,0 +1,6 @@ +import { LitElement } from 'lit'; +import { UmbElementMixin } from './element.mixin'; + +export abstract class UmbLitElement extends UmbElementMixin(LitElement) { + +} diff --git a/src/Umbraco.Web.UI.Client/src/core/extensions-api/registry/extension.registry.ts b/src/Umbraco.Web.UI.Client/src/core/extensions-api/registry/extension.registry.ts index 2b172dbb41..a7fa6e245a 100644 --- a/src/Umbraco.Web.UI.Client/src/core/extensions-api/registry/extension.registry.ts +++ b/src/Umbraco.Web.UI.Client/src/core/extensions-api/registry/extension.registry.ts @@ -69,10 +69,10 @@ export class UmbExtensionRegistry { return values.some((ext) => ext.alias === alias); } - getByAlias(alias: string): Observable; - getByAlias(alias: string) { + + getByAlias(alias: string): Observable { // TODO: make pipes prettier/simpler/reuseable - return this.extensions.pipe(map((dataTypes) => dataTypes.find((extension) => extension.alias === alias) || null)); + return this.extensions.pipe(map((dataTypes) => dataTypes.find((extension) => extension.alias === alias) || null)) as Observable; } // TODO: implement unregister of extension @@ -81,7 +81,7 @@ export class UmbExtensionRegistry { extensionsOfType(type: 'headerApp'): Observable>; extensionsOfType(type: 'section'): Observable>; extensionsOfType(type: 'sectionView'): Observable>; - extensionsOfType(type: 'tree'): Observable>; + extensionsOfType(type: 'tree'): Observable>; extensionsOfType(type: 'workspace'): Observable>; extensionsOfType(type: 'treeItemAction'): Observable>; extensionsOfType(type: 'dashboard'): Observable>; @@ -104,9 +104,9 @@ export class UmbExtensionRegistry { ); } - extensionsOfTypes(types: string[]): Observable> { + extensionsOfTypes(types: string[]): Observable> { return this.extensions.pipe( map((exts) => exts.filter((ext) => (types.indexOf(ext.type) !== -1)).sort((a, b) => (b.weight || 0) - (a.weight || 0))) - ); + ) as Observable>; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/install.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/install.handlers.ts index 5684fbb522..620ef6b26b 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/install.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/install.handlers.ts @@ -72,7 +72,7 @@ export const handlers = [ ); }), - rest.post(umbracoPath('/install/validateDatabase'), async (req, res, ctx) => { + rest.post(umbracoPath('/install/validate-database'), async (req, res, ctx) => { const body = await req.json(); if (body.name === 'validate') { diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts index 922d32db43..4991795055 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts @@ -72,7 +72,7 @@ export class UmbModalLayoutContentPickerElement extends UmbModalLayoutElement +
@@ -86,7 +86,7 @@ export class UmbModalLayoutContentPickerElement extends UmbModalLayoutElement
-
+ `; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/icon-picker/modal-layout-icon-picker.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/icon-picker/modal-layout-icon-picker.element.ts index 9559ec62a9..f47294058e 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/icon-picker/modal-layout-icon-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/icon-picker/modal-layout-icon-picker.element.ts @@ -165,7 +165,7 @@ export class UmbModalLayoutIconPickerElement extends UmbModalLayoutElement +
${this.renderSearchbar()}
@@ -180,7 +180,7 @@ export class UmbModalLayoutIconPickerElement extends UmbModalLayoutElement Save - + `; } diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout-change-password.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout-change-password.element.ts index 1d64f950d5..927af7e900 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout-change-password.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout-change-password.element.ts @@ -1,16 +1,15 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, CSSResultGroup, html, LitElement, nothing } from 'lit'; +import { css, CSSResultGroup, html, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { UmbModalHandler } from '..'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; export interface UmbModalChangePasswordData { requireOldPassword: boolean; } @customElement('umb-modal-layout-change-password') -export class UmbModalLayoutChangePasswordElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbModalLayoutChangePasswordElement extends UmbLitElement { static styles: CSSResultGroup = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout-current-user.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout-current-user.element.ts index b0a1140943..df4e320f0c 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout-current-user.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout-current-user.element.ts @@ -1,18 +1,17 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, CSSResultGroup, html, LitElement, nothing } from 'lit'; +import { css, CSSResultGroup, html, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { UmbModalHandler, UmbModalService } from '..'; import type { UserDetails } from '@umbraco-cms/models'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import { UmbCurrentUserHistoryStore, UmbCurrentUserHistoryItem, } from 'src/backoffice/users/current-user/current-user-history.store'; import { UmbCurrentUserStore } from 'src/backoffice/users/current-user/current-user.store'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-modal-layout-current-user') -export class UmbModalLayoutCurrentUserElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbModalLayoutCurrentUserElement extends UmbLitElement { static styles: CSSResultGroup = [ UUITextStyles, css` @@ -20,7 +19,7 @@ export class UmbModalLayoutCurrentUserElement extends UmbContextConsumerMixin(Um display: block; } :host, - umb-workspace-entity { + umb-workspace-layout { width: 100%; height: 100%; } @@ -103,13 +102,13 @@ export class UmbModalLayoutCurrentUserElement extends UmbContextConsumerMixin(Um private async _observeCurrentUser() { if (!this._currentUserStore) return; - this.observe(this._currentUserStore.currentUser, (currentUser) => { + this.observe(this._currentUserStore.currentUser, (currentUser) => { this._currentUser = currentUser; }); } private async _observeHistory() { if (this._currentUserHistoryStore) { - this.observe>(this._currentUserHistoryStore.getLatestHistory(), (history) => { + this.observe(this._currentUserHistoryStore.getLatestHistory(), (history) => { this._history = history; }); } @@ -160,7 +159,7 @@ export class UmbModalLayoutCurrentUserElement extends UmbContextConsumerMixin(Um render() { return html` - +
Your profile @@ -185,7 +184,7 @@ export class UmbModalLayoutCurrentUserElement extends UmbContextConsumerMixin(Um Close Logout
-
+ `; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout.element.ts index 14bfad9889..bf28bcae3a 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/modal-layout.element.ts @@ -1,9 +1,9 @@ -import { LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { UmbModalHandler } from '..'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-modal-layout') -export class UmbModalLayoutElement extends LitElement { +export class UmbModalLayoutElement extends UmbLitElement { @property({ attribute: false }) modalHandler?: UmbModalHandler; diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-section/picker-layout-section.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-section/picker-layout-section.element.ts index 6cce75097b..303bec8507 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-section/picker-layout-section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-section/picker-layout-section.element.ts @@ -2,13 +2,11 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbModalLayoutPickerBase } from '../modal-layout-picker-base'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; import type { ManifestSection } from '@umbraco-cms/models'; @customElement('umb-picker-layout-section') -export class UmbPickerLayoutSectionElement extends UmbContextConsumerMixin(UmbObserverMixin(UmbModalLayoutPickerBase)) { +export class UmbPickerLayoutSectionElement extends UmbModalLayoutPickerBase { static styles = [ UUITextStyles, css` @@ -67,7 +65,7 @@ export class UmbPickerLayoutSectionElement extends UmbContextConsumerMixin(UmbOb render() { return html` - +
@@ -88,7 +86,7 @@ export class UmbPickerLayoutSectionElement extends UmbContextConsumerMixin(UmbOb
- +
`; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts index 4f862a1e30..bec3735aa7 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts @@ -2,15 +2,11 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbModalLayoutPickerBase } from '../modal-layout-picker-base'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import type { UserGroupDetails } from '@umbraco-cms/models'; import { UmbUserGroupStore } from 'src/backoffice/users/user-groups/user-group.store'; @customElement('umb-picker-layout-user-group') -export class UmbPickerLayoutUserGroupElement extends UmbContextConsumerMixin( - UmbObserverMixin(UmbModalLayoutPickerBase) -) { +export class UmbPickerLayoutUserGroupElement extends UmbModalLayoutPickerBase { static styles = [ UUITextStyles, css` @@ -72,7 +68,7 @@ export class UmbPickerLayoutUserGroupElement extends UmbContextConsumerMixin( private _observeUserGroups() { if (!this._userGroupStore) return; - this.observe>( + this.observe( this._userGroupStore.getAll(), (userGroups) => (this._userGroups = userGroups) ); @@ -80,7 +76,7 @@ export class UmbPickerLayoutUserGroupElement extends UmbContextConsumerMixin( render() { return html` - +
@@ -102,7 +98,7 @@ export class UmbPickerLayoutUserGroupElement extends UmbContextConsumerMixin( -
+ `; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts index 931181e386..f9ce297c34 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts @@ -3,12 +3,10 @@ import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbModalLayoutPickerBase } from '../modal-layout-picker-base'; import type { UserDetails } from '@umbraco-cms/models'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; import { UmbUserStore } from 'src/backoffice/users/users/user.store'; @customElement('umb-picker-layout-user') -export class UmbPickerLayoutUserElement extends UmbContextConsumerMixin(UmbObserverMixin(UmbModalLayoutPickerBase)) { +export class UmbPickerLayoutUserElement extends UmbModalLayoutPickerBase { static styles = [ UUITextStyles, css` @@ -75,12 +73,12 @@ export class UmbPickerLayoutUserElement extends UmbContextConsumerMixin(UmbObser private _observeUsers() { if (!this._userStore) return; - this.observe>(this._userStore.getAll(), (users) => (this._users = users)); + this.observe(this._userStore.getAll(), (users) => (this._users = users)); } render() { return html` - +
@@ -102,7 +100,7 @@ export class UmbPickerLayoutUserElement extends UmbContextConsumerMixin(UmbObser -
+ `; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element.ts index 78782e1322..4df01afad1 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element.ts @@ -1,14 +1,13 @@ -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { groupBy } from 'lodash'; import type { UUIInputEvent } from '@umbraco-ui/uui'; import type { UmbModalHandler } from '../../modal-handler'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { ManifestPropertyEditorUI } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; +import { UmbLitElement } from '@umbraco-cms/element'; export interface UmbModalPropertyEditorUIPickerData { selection?: Array; @@ -21,7 +20,7 @@ interface GroupedPropertyEditorUIs { // TODO: make use of UmbPickerLayoutBase @customElement('umb-modal-layout-property-editor-ui-picker') -export class UmbModalLayoutPropertyEditorUIPickerElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbModalLayoutPropertyEditorUIPickerElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -116,7 +115,7 @@ export class UmbModalLayoutPropertyEditorUIPickerElement extends UmbContextConsu private _usePropertyEditorUIs() { if (!this.data) return; - this.observe( + this.observe( umbExtensionsRegistry.extensionsOfType('propertyEditorUI'), (propertyEditorUIs) => { this._propertyEditorUIs = propertyEditorUIs; @@ -158,13 +157,13 @@ export class UmbModalLayoutPropertyEditorUIPickerElement extends UmbContextConsu render() { return html` - + ${this._renderFilter()} ${this._renderGrid()}
-
+ `; } diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/modal.stories.mdx b/src/Umbraco.Web.UI.Client/src/core/modal/modal.stories.mdx index 91b54b1883..f150e2be2a 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/modal.stories.mdx +++ b/src/Umbraco.Web.UI.Client/src/core/modal/modal.stories.mdx @@ -24,9 +24,9 @@ The UmbModal service can be used to open modals. ```ts import { html, LitElement } from 'lit'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; import type { UmbModalService } from './core/services/modal'; -class MyElement extends UmbContextConsumerMixin(LitElement) { +class MyElement extends UmbLitElement { private _modalService_?: UmbModalService; constructor() { super(); @@ -44,9 +44,9 @@ A modal is opened by calling one of the helper methods on the UmbModalService. T ```ts import { html, LitElement } from 'lit'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; import type { UmbModalService } from './core/services/modal'; -class MyElement extends UmbContextConsumerMixin(LitElement) { +class MyElement extends UmbLitElement { private _modalService?: UmbModalService; constructor() { super(); diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/modal.stories.ts b/src/Umbraco.Web.UI.Client/src/core/modal/modal.stories.ts index 00621d3e07..891322ce64 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/modal.stories.ts @@ -1,10 +1,9 @@ import { Meta, Story } from '@storybook/web-components'; -import { LitElement } from 'lit'; import { html } from 'lit-html'; import { customElement, property, state } from 'lit/decorators.js'; import { UmbModalService } from '.'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; export default { title: 'API/Modals', @@ -18,7 +17,7 @@ export default { } as Meta; @customElement('story-modal-service-example') -export class StoryModalServiceExampleElement extends UmbContextConsumerMixin(LitElement) { +export class StoryModalServiceExampleElement extends UmbLitElement { @property() modalLayout = 'confirm'; diff --git a/src/Umbraco.Web.UI.Client/src/core/notification/notification.stories.mdx b/src/Umbraco.Web.UI.Client/src/core/notification/notification.stories.mdx index 9556c3e4bc..1809a23ce2 100644 --- a/src/Umbraco.Web.UI.Client/src/core/notification/notification.stories.mdx +++ b/src/Umbraco.Web.UI.Client/src/core/notification/notification.stories.mdx @@ -20,10 +20,10 @@ The UmbNotification service can be used to open notifications. ```ts import { html, LitElement } from 'lit'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; import type { UmbNotificationService } from './core/services/notification'; -class MyElement extends UmbContextConsumerMixin(LitElement) { +class MyElement extends UmbLitElement { private _notificationService?: UmbNotificationService; constructor() { @@ -44,10 +44,10 @@ A notification is opened by calling one of the helper methods on the UmbNotifica ```ts import { html, LitElement } from 'lit'; import { state } from 'lit/decorators.js'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; import type { UmbNotificationService, UmbNotificationDefaultData } from './core/services/notification'; -class MyElement extends UmbContextConsumerMixin(LitElement) { +class MyElement extends UmbLitElement { private _notificationService?: UmbNotificationService; constructor() { diff --git a/src/Umbraco.Web.UI.Client/src/core/notification/notification.stories.ts b/src/Umbraco.Web.UI.Client/src/core/notification/notification.stories.ts index 2c4053718a..a2103c9988 100644 --- a/src/Umbraco.Web.UI.Client/src/core/notification/notification.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/core/notification/notification.stories.ts @@ -3,13 +3,13 @@ import '../context-api/provide/context-provider.element'; import './layouts/default'; import { Meta, Story } from '@storybook/web-components'; -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { customElement } from 'lit/decorators.js'; -import { UmbContextConsumerMixin } from '../context-api'; - import type { UmbNotificationDefaultData } from './layouts/default'; import { UmbNotificationColor, UmbNotificationOptions, UmbNotificationService } from '.'; +import { UmbLitElement } from '@umbraco-cms/element'; + export default { title: 'API/Notifications/Overview', component: 'ucp-notification-layout-default', @@ -22,7 +22,7 @@ export default { } as Meta; @customElement('story-notification-default-example') -export class StoryNotificationDefaultExampleElement extends UmbContextConsumerMixin(LitElement) { +export class StoryNotificationDefaultExampleElement extends UmbLitElement { private _notificationService?: UmbNotificationService; connectedCallback(): void { diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/index.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/index.ts index ffe08ff811..6795436570 100644 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/index.ts +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/index.ts @@ -1 +1,2 @@ -export * from './observer.mixin'; +export * from './observer.controller'; +export * from './observer'; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.controller.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.controller.ts index cdd7042b7b..fe7086d9fb 100644 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.controller.ts @@ -4,10 +4,11 @@ import type { UmbControllerInterface } from 'src/core/controller/controller.inte import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; -export class UmbObserverController extends UmbObserver implements UmbControllerInterface { +export class UmbObserverController extends UmbObserver implements UmbControllerInterface { - constructor(host:UmbControllerHostInterface, source: Observable, callback: (_value: Y) => void) { + constructor(host:UmbControllerHostInterface, source: Observable, callback: (_value: T) => void) { super(source, callback); + // TODO: What should happen if source or some? identifier is already present? host.addController(this); } diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.mixin.ts deleted file mode 100644 index 3e2be5bfed..0000000000 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.mixin.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Observable, Subscription } from 'rxjs'; -import type { HTMLElementConstructor } from '../models'; - -export declare class UmbObserverMixinInterface { - observe(source: Observable, callback: (_value: Y) => void): () => void; -} - -export const UmbObserverMixin = (superClass: T) => { - class UmbObserverMixinClass extends superClass { - _subscriptions: Map, Subscription> = new Map(); - - observe(source: Observable, callback: (_value: Y) => void): ()=>void { - - // TODO: can be transferred to something using alias? - /* - if (this._subscriptions.has(source)) { - const subscription = this._subscriptions.get(source); - subscription?.unsubscribe(); - } - */ - - const subscription = source.subscribe((value) => callback(value)); - this._subscriptions.set(source, subscription); - - return subscription.unsubscribe; - } - - disconnectedCallback() { - super.disconnectedCallback?.(); - this._subscriptions.forEach((subscription) => subscription.unsubscribe()); - } - } - - return UmbObserverMixinClass as unknown as HTMLElementConstructor & T; -}; - -declare global { - interface HTMLElement { - connectedCallback(): void; - disconnectedCallback(): void; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.ts index b577a2b6d3..d21a9359a0 100644 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.ts +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.ts @@ -1,10 +1,10 @@ import { Observable, Subscription } from 'rxjs'; -export class UmbObserver { +export class UmbObserver { #subscription!: Subscription; - constructor(source: Observable, callback: (_value: Y) => void) { + constructor(source: Observable, callback: (_value: T) => void) { // TODO: can be transferred to something using alias? /* diff --git a/src/Umbraco.Web.UI.Client/src/core/resources/index.ts b/src/Umbraco.Web.UI.Client/src/core/resources/index.ts index b4dfcb2097..b5aa810083 100644 --- a/src/Umbraco.Web.UI.Client/src/core/resources/index.ts +++ b/src/Umbraco.Web.UI.Client/src/core/resources/index.ts @@ -1 +1,3 @@ export * from './resource.controller'; +export * from './tryExecute.method'; +export * from './tryExecuteAndNotify.method'; diff --git a/src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts b/src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts index 06cf06721e..5668c41180 100644 --- a/src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts @@ -6,25 +6,6 @@ import { ApiError, CancelablePromise, ProblemDetails } from '@umbraco-cms/backen import { UmbNotificationOptions, UmbNotificationService } from 'src/core/notification'; import { UmbNotificationDefaultData } from 'src/core/notification/layouts/default'; -/** - * Extract the ProblemDetails object from an ApiError. - * - * This assumes that all ApiErrors contain a ProblemDetails object in their body. - */ -function toProblemDetails(error: unknown): ProblemDetails | undefined { - if (error instanceof ApiError) { - const errorDetails = error.body as ProblemDetails; - return errorDetails; - } else if (error instanceof Error) { - return { - title: error.name, - detail: error.message, - }; - } - - return undefined; -} - export class UmbResourceController extends UmbController { #promise: Promise; @@ -40,22 +21,41 @@ export class UmbResourceController extends UmbController { }); } - hostConnected() { - this.hostConnected(); + hostConnected(): void { + // Do nothing } - hostDisconnected() { + hostDisconnected(): void { this.cancel(); } /** - * Wrap the {execute} function in a try/catch block and return a tuple with the result and the error. + * Extract the ProblemDetails object from an ApiError. + * + * This assumes that all ApiErrors contain a ProblemDetails object in their body. */ - async tryExecute(): Promise<{ data?: T; error?: ProblemDetails }> { + static toProblemDetails(error: unknown): ProblemDetails | undefined { + if (error instanceof ApiError) { + const errorDetails = error.body as ProblemDetails; + return errorDetails; + } else if (error instanceof Error) { + return { + title: error.name, + detail: error.message, + }; + } + + return undefined; + } + + /** + * Base execute function with a try/catch block and return a tuple with the result and the error. + */ + static async tryExecute(promise: Promise): Promise<{ data?: T; error?: ProblemDetails }> { try { - return { data: await this.#promise }; + return { data: await promise }; } catch (e) { - return { error: toProblemDetails(e) }; + return { error: UmbResourceController.toProblemDetails(e) }; } } @@ -64,7 +64,7 @@ export class UmbResourceController extends UmbController { * If the executor function throws an error, then show the details in a notification. */ async tryExecuteAndNotify(options?: UmbNotificationOptions): Promise<{ data?: T; error?: ProblemDetails }> { - const { data, error } = await this.tryExecute(); + const { data, error } = await UmbResourceController.tryExecute(this.#promise); if (error) { const data: UmbNotificationDefaultData = { diff --git a/src/Umbraco.Web.UI.Client/src/core/resources/tryExecute.method.ts b/src/Umbraco.Web.UI.Client/src/core/resources/tryExecute.method.ts new file mode 100644 index 0000000000..a65503d760 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/resources/tryExecute.method.ts @@ -0,0 +1,5 @@ +import { UmbResourceController } from './resource.controller'; + +export function tryExecute(promise: Promise) { + return UmbResourceController.tryExecute(promise); +} diff --git a/src/Umbraco.Web.UI.Client/src/core/resources/tryExecuteAndNotify.method.ts b/src/Umbraco.Web.UI.Client/src/core/resources/tryExecuteAndNotify.method.ts index 8c1bb647c9..ac1b9c860d 100644 --- a/src/Umbraco.Web.UI.Client/src/core/resources/tryExecuteAndNotify.method.ts +++ b/src/Umbraco.Web.UI.Client/src/core/resources/tryExecuteAndNotify.method.ts @@ -1,12 +1,12 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { UmbControllerHostInterface } from '../controller/controller-host.mixin'; -import type { ProblemDetails } from '../backend-api/models/ProblemDetails'; import { UmbResourceController } from './resource.controller'; import { UmbNotificationOptions } from 'src/core/notification'; -export async function tryExecuteAndNotify( +export function tryExecuteAndNotify( host: UmbControllerHostInterface, resource: Promise, options?: UmbNotificationOptions -): Promise<{ data?: T; error?: ProblemDetails }> { - return await new UmbResourceController(host, resource).tryExecuteAndNotify(options); +) { + return new UmbResourceController(host, resource).tryExecuteAndNotify(options); } diff --git a/src/Umbraco.Web.UI.Client/src/installer/consent/installer-consent.element.ts b/src/Umbraco.Web.UI.Client/src/installer/consent/installer-consent.element.ts index 708395f34b..f42628e2b1 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/consent/installer-consent.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/consent/installer-consent.element.ts @@ -1,14 +1,13 @@ -import { css, CSSResultGroup, html, LitElement } from 'lit'; +import { css, CSSResultGroup, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { UmbInstallerContext } from '../installer.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { ConsentLevel, Install, InstallSettings, Telemetry, TelemetryLevel } from '@umbraco-cms/backend-api'; +import { ConsentLevel, Telemetry, TelemetryLevel } from '@umbraco-cms/backend-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-installer-consent') -export class UmbInstallerConsentElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbInstallerConsentElement extends UmbLitElement { static styles: CSSResultGroup = [ css` :host, @@ -66,7 +65,7 @@ export class UmbInstallerConsentElement extends UmbContextConsumerMixin(UmbObser private _observeInstallerSettings() { if (!this._installerContext) return; - this.observe(this._installerContext.settings, (settings) => { + this.observe(this._installerContext.settings, (settings) => { this._telemetryLevels = settings.user?.consentLevels ?? []; }); } @@ -74,7 +73,7 @@ export class UmbInstallerConsentElement extends UmbContextConsumerMixin(UmbObser private _observeInstallerData() { if (!this._installerContext) return; - this.observe(this._installerContext.data, (data) => { + this.observe(this._installerContext.data, (data) => { this._telemetryFormData = data.telemetryLevel; }); } diff --git a/src/Umbraco.Web.UI.Client/src/installer/database/installer-database.element.ts b/src/Umbraco.Web.UI.Client/src/installer/database/installer-database.element.ts index 995e231b87..464f959d06 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/database/installer-database.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/database/installer-database.element.ts @@ -1,22 +1,19 @@ import { UUIButtonElement } from '@umbraco-ui/uui'; -import { css, CSSResultGroup, html, LitElement, nothing } from 'lit'; +import { css, CSSResultGroup, html, nothing } from 'lit'; import { customElement, property, query, state } from 'lit/decorators.js'; import { UmbInstallerContext } from '../installer.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import { - ApiError, DatabaseInstall, DatabaseSettings, - Install, InstallResource, - InstallSettings, ProblemDetails, } from '@umbraco-cms/backend-api'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { tryExecute } from '@umbraco-cms/resources'; @customElement('umb-installer-database') -export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbInstallerDatabaseElement extends UmbLitElement { static styles: CSSResultGroup = [ css` :host, @@ -88,7 +85,7 @@ export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObse public databaseFormData!: DatabaseInstall; @state() - private _options: { name: string; value: string; selected?: boolean }[] = []; + private _options: Option[] = []; @state() private _databases: DatabaseSettings[] = []; @@ -114,18 +111,21 @@ export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObse private _observeInstallerSettings() { if (!this._installerContext) return; - this.observe(this._installerContext.settings, (settings) => { - this._databases = settings.databases ?? []; + this.observe(this._installerContext.settings, (settings) => { + this._databases = settings?.databases ?? []; // If there is an isConfigured database in the databases array then we can skip the database selection step // and just use that. this._preConfiguredDatabase = this._databases.find((x) => x.isConfigured); if (!this._preConfiguredDatabase) { - this._options = this._databases.map((x, i) => ({ - name: x.displayName ?? 'Unknown database', - value: x.id!, - selected: i === 0, - })); + this._options = this._databases + .filter((x) => !!x.id) + .map((x, i) => ({ + name: x.displayName ?? 'Unknown database', + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + value: x.id!, + selected: i === 0, + })); } }); } @@ -133,9 +133,9 @@ export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObse private _observeInstallerData() { if (!this._installerContext) return; - this.observe(this._installerContext.data, (data) => { - this.databaseFormData = data.database ?? {}; - this._options.forEach((x, i) => (x.selected = data.database?.id === x.value || i === 0)); + this.observe(this._installerContext.data, (data) => { + this.databaseFormData = data?.database ?? ({} as DatabaseInstall); + this._options.forEach((x, i) => (x.selected = data?.database?.id === x.value || i === 0)); }); } @@ -190,27 +190,23 @@ export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObse } if (selectedDatabase.requiresConnectionTest) { - try { - const databaseDetails: DatabaseInstall = { - id, - username, - password, - server, - useIntegratedAuthentication, - name, - connectionString, - providerName: selectedDatabase.providerName, - }; + const databaseDetails: DatabaseInstall = { + id, + username, + password, + server, + useIntegratedAuthentication, + name, + connectionString, + providerName: selectedDatabase.providerName, + }; - await InstallResource.postInstallValidateDatabase({ requestBody: databaseDetails }); - } catch (e) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - console.warn('Database validation failed', error.detail); - this._validationErrorMessage = error.detail ?? 'Could not verify database connection'; - } else { - this._validationErrorMessage = 'A server error happened when trying to validate the database'; - } + const { error } = await tryExecute( + InstallResource.postInstallValidateDatabase({ requestBody: databaseDetails }) + ); + + if (error) { + this._validationErrorMessage = `The server could not validate the database connection. Details: ${error.detail}`; this._installButton.state = 'failed'; return; } @@ -228,30 +224,31 @@ export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObse providerName: selectedDatabase.providerName, }; - this._installerContext?.appendData({ database }); + this._installerContext.appendData({ database }); } - this._installerContext?.nextStep(); - this._installerContext - .requestInstall() - .then(() => this._handleFulfilled()) - .catch((error: unknown) => this._handleRejected(error)); + this._installerContext.nextStep(); + + const { error } = await tryExecute( + InstallResource.postInstallSetup({ requestBody: this._installerContext.getData() }) + ); + + if (error) { + this._handleRejected(error); + } else { + this._handleFulfilled(); + } }; private _handleFulfilled() { + // TODO: The post install will probably return a user in the future, so we have to set that context somewhere to let the client know that it is authenticated console.warn('TODO: Set up real authentication'); sessionStorage.setItem('is-authenticated', 'true'); history.replaceState(null, '', '/content'); } - private _handleRejected(e: unknown) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - if (e.status === 400) { - this._installerContext?.setInstallStatus(error); - } - } - this._installerContext?.nextStep(); + private _handleRejected(e: ProblemDetails) { + this._installerContext?.setInstallStatus(e); } private _onBack() { diff --git a/src/Umbraco.Web.UI.Client/src/installer/error/installer-error.element.ts b/src/Umbraco.Web.UI.Client/src/installer/error/installer-error.element.ts index 5063330362..80c3aaba34 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/error/installer-error.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/error/installer-error.element.ts @@ -1,14 +1,12 @@ -import { css, CSSResultGroup, html, LitElement, nothing } from 'lit'; +import { css, CSSResultGroup, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbInstallerContext } from '../installer.context'; import { ProblemDetails } from '@umbraco-cms/backend-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; - +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-installer-error') -export class UmbInstallerErrorElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbInstallerErrorElement extends UmbLitElement { static styles: CSSResultGroup = [ css` :host, @@ -23,7 +21,7 @@ export class UmbInstallerErrorElement extends UmbContextConsumerMixin(UmbObserve } #error-message { - color: var(--uui-color-error, red); + color: var(--uui-color-danger, #d42054); } `, ]; diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.context.ts b/src/Umbraco.Web.UI.Client/src/installer/installer.context.ts index 717ae07817..2a0db363fa 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.context.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.context.ts @@ -1,5 +1,6 @@ import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs'; import { Install, InstallResource, InstallSettings, ProblemDetails, TelemetryLevel } from '@umbraco-cms/backend-api'; +import { tryExecute } from '@umbraco-cms/resources'; /** * Context API for the installer @@ -71,8 +72,8 @@ export class UmbInstallerContext { * @memberof UmbInstallerContext */ public reset(): void { - this._currentStep.next(1); this._installStatus.next(null); + this._currentStep.next(1); } /** @@ -95,17 +96,6 @@ export class UmbInstallerContext { return this._data.getValue(); } - /** - * Post the installation data to the API - * @public - * @return {*} - * @memberof UmbInstallerContext - */ - public requestInstall() { - // TODO: The post install will probably return a user in the future, so we have to set that context somewhere to let the client know that it is authenticated - return InstallResource.postInstallSetup({ requestBody: this.getData() }); - } - /** * Set the install status * @public @@ -121,9 +111,13 @@ export class UmbInstallerContext { * @private * @memberof UmbInstallerContext */ - private _loadInstallerSettings() { - InstallResource.getInstallSettings().then((installSettings) => { - this._settings.next(installSettings); - }); + private async _loadInstallerSettings() { + const { data, error } = await tryExecute(InstallResource.getInstallSettings()); + if (data) { + this._settings.next(data); + } else if (error) { + console.error(error.detail, error); + this._installStatus.next(error); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts b/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts index 66e04f97bf..4739205ed6 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts @@ -1,8 +1,7 @@ -import { css, CSSResultGroup, html, LitElement } from 'lit'; +import { css, CSSResultGroup, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbInstallerContext } from './installer.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextProviderMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; import './consent/installer-consent.element'; import './database/installer-database.element'; @@ -12,7 +11,7 @@ import './shared/layout/installer-layout.element'; import './user/installer-user.element'; @customElement('umb-installer') -export class UmbInstallerElement extends UmbContextProviderMixin(UmbObserverMixin(LitElement)) { +export class UmbInstallerElement extends UmbLitElement { static styles: CSSResultGroup = [css``]; @state() @@ -31,9 +30,15 @@ export class UmbInstallerElement extends UmbContextProviderMixin(UmbObserverMixi } private _observeCurrentStep() { - this.observe(this._umbInstallerContext.currentStepChanges(), (step) => { + this.observe(this._umbInstallerContext.currentStepChanges(), (step) => { this.step = step; }); + + this.observe(this._umbInstallerContext.installStatusChanges(), (error) => { + if (error) { + this.step = 5; + } + }); } private _renderSection() { diff --git a/src/Umbraco.Web.UI.Client/src/installer/user/installer-user.element.ts b/src/Umbraco.Web.UI.Client/src/installer/user/installer-user.element.ts index f211f8c820..68df375741 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/user/installer-user.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/user/installer-user.element.ts @@ -1,13 +1,11 @@ -import { css, CSSResultGroup, html, LitElement } from 'lit'; +import { css, CSSResultGroup, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbInstallerContext } from '../installer.context'; -import { Install } from '@umbraco-cms/backend-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-installer-user') -export class UmbInstallerUserElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbInstallerUserElement extends UmbLitElement { static styles: CSSResultGroup = [ css` :host, @@ -75,7 +73,7 @@ export class UmbInstallerUserElement extends UmbContextConsumerMixin(UmbObserver private _observeInstallerData() { if (!this._installerContext) return; - this.observe(this._installerContext.data, ({ user }) => { + this.observe(this._installerContext.data, ({ user }) => { this._userFormData = { name: user.name, password: user.password, diff --git a/src/Umbraco.Web.UI.Client/src/upgrader/upgrader.element.ts b/src/Umbraco.Web.UI.Client/src/upgrader/upgrader.element.ts index 803a2a4ae5..4374ffccc6 100644 --- a/src/Umbraco.Web.UI.Client/src/upgrader/upgrader.element.ts +++ b/src/Umbraco.Web.UI.Client/src/upgrader/upgrader.element.ts @@ -1,15 +1,17 @@ import '../installer/shared/layout/installer-layout.element'; import './upgrader-view.element'; -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { ApiError, ProblemDetails, UpgradeResource, UpgradeSettings } from '@umbraco-cms/backend-api'; +import { UpgradeResource, UpgradeSettings } from '@umbraco-cms/backend-api'; +import { tryExecute } from '@umbraco-cms/resources'; +import { UmbLitElement } from '@umbraco-cms/element'; /** * @element umb-upgrader */ @customElement('umb-upgrader') -export class UmbUpgrader extends LitElement { +export class UmbUpgrader extends UmbLitElement { @state() private upgradeSettings?: UpgradeSettings; @@ -41,14 +43,12 @@ export class UmbUpgrader extends LitElement { private async _setup() { this.fetching = true; - try { - const data = await UpgradeResource.getUpgradeSettings(); + const { data, error } = await tryExecute(UpgradeResource.getUpgradeSettings()); + + if (data) { this.upgradeSettings = data; - } catch (e) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - this.errorMessage = error.detail; - } + } else if (error) { + this.errorMessage = error.detail; } this.fetching = false; @@ -59,18 +59,12 @@ export class UmbUpgrader extends LitElement { this.errorMessage = ''; this.upgrading = true; - try { - await UpgradeResource.postUpgradeAuthorize(); + const { error } = await tryExecute(UpgradeResource.postUpgradeAuthorize()); + + if (error) { + this.errorMessage = error.detail || 'Unknown error, please try again'; + } else { history.pushState(null, '', '/'); - } catch (e) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - if (e.status === 400) { - this.errorMessage = error.detail || 'Unknown error, please try again'; - } - } else { - this.errorMessage = 'Unknown error, please try again'; - } } this.upgrading = false; diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 6f278ee1d3..3b5a74a536 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -23,6 +23,7 @@ "@umbraco-cms/models": ["src/core/models"], "@umbraco-cms/backend-api": ["src/core/backend-api"], "@umbraco-cms/context-api": ["src/core/context-api"], + "@umbraco-cms/element": ["src/core/element"], "@umbraco-cms/extensions-api": ["src/core/extensions-api"], "@umbraco-cms/extensions-registry": ["src/core/extensions-registry"], "@umbraco-cms/observable-api": ["src/core/observable-api"], 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 bf55d97a2d..c4bef5c1ca 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -14,12 +14,12 @@ export default { '@umbraco-cms/models': './src/core/models/index.ts', '@umbraco-cms/backend-api': './src/core/backend-api/index.ts', '@umbraco-cms/context-api': './src/core/context-api/index.ts', + '@umbraco-cms/element': './src/core/element/index.ts', '@umbraco-cms/extensions-api': './src/core/extensions-api/index.ts', '@umbraco-cms/observable-api': './src/core/observable-api/index.ts', - '@umbraco-cms/resource-api': './src/core/resource-api', '@umbraco-cms/utils': './src/core/utils/index.ts', '@umbraco-cms/test-utils': './src/core/test-utils/index.ts', - '@umbraco-cms/resources': './src/core/resources', + '@umbraco-cms/resources': './src/core/resources/index.ts', '@umbraco-cms/services': './src/core/services', '@umbraco-cms/extensions-registry': './src/core/extensions-registry/index.ts', },