diff --git a/src/Umbraco.Web.UI.Client/.eslintrc.json b/src/Umbraco.Web.UI.Client/.eslintrc.json index 32cc406cec..55525707e8 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc.json +++ b/src/Umbraco.Web.UI.Client/.eslintrc.json @@ -2,7 +2,7 @@ "ignorePatterns": ["vite.*.ts"], "root": true, "extends": ["eslint:recommended", "plugin:import/recommended", "prettier"], - "plugins": ["import"], + "plugins": ["import", "eslint-plugin-local-rules"], "overrides": [ { "files": ["**/*.ts"], @@ -31,7 +31,8 @@ }, "rules": { "no-var": "error", - "import/no-unresolved": "error" + "import/no-unresolved": "error", + "local-rules/bad-type-import": "error" }, "settings": { "import/parsers": { diff --git a/src/Umbraco.Web.UI.Client/.vscode/launch.json b/src/Umbraco.Web.UI.Client/.vscode/launch.json new file mode 100644 index 0000000000..094ba1adaf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:5173", + "webRoot": "${workspaceFolder}" + } + ] +} diff --git a/src/Umbraco.Web.UI.Client/e2e/installer.spec.ts b/src/Umbraco.Web.UI.Client/e2e/installer.spec.ts index 9f701c17f3..b504058db8 100644 --- a/src/Umbraco.Web.UI.Client/e2e/installer.spec.ts +++ b/src/Umbraco.Web.UI.Client/e2e/installer.spec.ts @@ -1,9 +1,10 @@ import { rest } from 'msw'; import umbracoPath from '../src/core/helpers/umbraco-path'; -import { ProblemDetails, StatusResponse } from '../src/core/models'; import { expect, test } from '../test'; +import type { ProblemDetails, StatusResponse } from '../src/core/models'; + test.describe('installer tests', () => { test.beforeEach(async ({ page, worker }) => { await worker.use( diff --git a/src/Umbraco.Web.UI.Client/e2e/upgrader.spec.ts b/src/Umbraco.Web.UI.Client/e2e/upgrader.spec.ts index 8421f333e7..43ee82f78e 100644 --- a/src/Umbraco.Web.UI.Client/e2e/upgrader.spec.ts +++ b/src/Umbraco.Web.UI.Client/e2e/upgrader.spec.ts @@ -1,7 +1,7 @@ import { rest } from 'msw'; import umbracoPath from '../src/core/helpers/umbraco-path'; -import { ProblemDetails, StatusResponse } from '../src/core/models'; +import type { ProblemDetails, StatusResponse } from '../src/core/models'; import { expect, test } from '../test'; test.describe('upgrader tests', () => { diff --git a/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs b/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs new file mode 100644 index 0000000000..eeddac50c8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs @@ -0,0 +1,36 @@ +'use strict'; + +/* + * 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 = { + 'bad-type-import': { + meta: { + type: 'problem', + docs: { + description: 'Ensures the use of the `import type` operator from the `src/core/models/index.ts` file.', + category: 'Best Practices', + recommended: true, + }, + fixable: 'code', + schema: [], + }, + 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')) { + const sourceCode = context.getSourceCode(); + const nodeSource = sourceCode.getText(node); + context.report({ + node, + message: 'Use `import type` instead of `import`.', + fix: fixer => fixer.replaceText(node, nodeSource.replace('import', 'import type')), + }); + } + }, + }; + } + } +}; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 4faa01f6d5..b3af37461e 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -49,6 +49,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-lit": "^1.6.1", "eslint-plugin-lit-a11y": "^2.2.2", + "eslint-plugin-local-rules": "^1.3.1", "eslint-plugin-storybook": "^0.6.4", "lit-html": "^2.3.1", "msw": "^0.45.0", @@ -12288,6 +12289,12 @@ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "dev": true }, + "node_modules/eslint-plugin-local-rules": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-local-rules/-/eslint-plugin-local-rules-1.3.1.tgz", + "integrity": "sha512-ezuHRUXzRwFY3jFaX9vz8vxLLdLLIrbXBnVM6rip71/zjnIBaExY2vsm316temX+P3tasjQ2ciadWOTnnOUCgA==", + "dev": true + }, "node_modules/eslint-plugin-storybook": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.4.tgz", @@ -35952,6 +35959,12 @@ } } }, + "eslint-plugin-local-rules": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-local-rules/-/eslint-plugin-local-rules-1.3.1.tgz", + "integrity": "sha512-ezuHRUXzRwFY3jFaX9vz8vxLLdLLIrbXBnVM6rip71/zjnIBaExY2vsm316temX+P3tasjQ2ciadWOTnnOUCgA==", + "dev": true + }, "eslint-plugin-storybook": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.4.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 271937fec6..37ad82bc6a 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -38,16 +38,16 @@ "dependencies": { "@umbraco-ui/uui": "^1.0.0", "@umbraco-ui/uui-css": "^1.0.0", + "@umbraco-ui/uui-modal": "file:umbraco-ui-uui-modal-0.0.0.tgz", + "@umbraco-ui/uui-modal-container": "file:umbraco-ui-uui-modal-container-0.0.0.tgz", + "@umbraco-ui/uui-modal-dialog": "file:umbraco-ui-uui-modal-dialog-0.0.0.tgz", + "@umbraco-ui/uui-modal-sidebar": "file:umbraco-ui-uui-modal-sidebar-0.0.0.tgz", "element-internals-polyfill": "^1.1.9", "lit": "^2.3.1", "openapi-typescript-fetch": "^1.1.3", "router-slot": "^1.5.5", "rxjs": "^7.5.6", - "uuid": "^8.3.2", - "@umbraco-ui/uui-modal": "file:umbraco-ui-uui-modal-0.0.0.tgz", - "@umbraco-ui/uui-modal-container": "file:umbraco-ui-uui-modal-container-0.0.0.tgz", - "@umbraco-ui/uui-modal-dialog": "file:umbraco-ui-uui-modal-dialog-0.0.0.tgz", - "@umbraco-ui/uui-modal-sidebar": "file:umbraco-ui-uui-modal-sidebar-0.0.0.tgz" + "uuid": "^8.3.2" }, "devDependencies": { "@babel/core": "^7.18.13", @@ -76,6 +76,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-lit": "^1.6.1", "eslint-plugin-lit-a11y": "^2.2.2", + "eslint-plugin-local-rules": "^1.3.1", "eslint-plugin-storybook": "^0.6.4", "lit-html": "^2.3.1", "msw": "^0.45.0", diff --git a/src/Umbraco.Web.UI.Client/schemas/api/api.yml b/src/Umbraco.Web.UI.Client/schemas/api/api.yml index 974f5dc74a..330f2db671 100644 --- a/src/Umbraco.Web.UI.Client/schemas/api/api.yml +++ b/src/Umbraco.Web.UI.Client/schemas/api/api.yml @@ -57,6 +57,22 @@ paths: application/json: schema: $ref: '#/components/schemas/ProblemDetails' + /manifests: + get: + operationId: Manifests + responses: + '200': + description: 200 response + content: + application/json: + schema: + $ref: '#/components/schemas/ManifestsResponse' + default: + description: default response + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' /server/status: get: operationId: GetStatus @@ -341,6 +357,240 @@ components: required: - user - telemetryLevel + MetaSection: + type: object + properties: + pathname: + type: string + weight: + type: number + format: float + required: + - pathname + - weight + IManifestSection: + type: object + properties: + type: + type: string + enum: + - section + meta: + $ref: '#/components/schemas/MetaSection' + name: + type: string + js: + type: string + elementName: + type: string + alias: + type: string + required: + - type + - meta + - name + - alias + MetaPropertyEditorUI: + type: object + properties: + icon: + type: string + group: + type: string + required: + - icon + - group + IManifestPropertyEditorUI: + type: object + properties: + type: + type: string + enum: + - propertyEditorUI + meta: + $ref: '#/components/schemas/MetaPropertyEditorUI' + name: + type: string + js: + type: string + elementName: + type: string + alias: + type: string + required: + - type + - meta + - name + - alias + MetaDashboard: + type: object + properties: + sections: + type: array + items: + type: string + pathname: + type: string + weight: + type: number + format: float + label: + type: string + required: + - sections + - pathname + - weight + IManifestDashboard: + type: object + properties: + type: + type: string + enum: + - dashboard + meta: + $ref: '#/components/schemas/MetaDashboard' + name: + type: string + js: + type: string + elementName: + type: string + alias: + type: string + required: + - type + - meta + - name + - alias + MetaEditorView: + type: object + properties: + editors: + type: array + items: + type: string + pathname: + type: string + weight: + type: number + format: float + icon: + type: string + required: + - editors + - pathname + - weight + - icon + IManifestEditorView: + type: object + properties: + type: + type: string + enum: + - editorView + meta: + $ref: '#/components/schemas/MetaEditorView' + name: + type: string + js: + type: string + elementName: + type: string + alias: + type: string + required: + - type + - meta + - name + - alias + MetaPropertyAction: + type: object + properties: + propertyEditors: + type: array + items: + type: string + required: + - propertyEditors + IManifestPropertyAction: + type: object + properties: + type: + type: string + enum: + - propertyAction + meta: + $ref: '#/components/schemas/MetaPropertyAction' + name: + type: string + js: + type: string + elementName: + type: string + alias: + type: string + required: + - type + - meta + - name + - alias + IManifestEntrypoint: + type: object + properties: + type: + type: string + enum: + - entrypoint + js: + type: string + alias: + type: string + required: + - type + - js + - alias + IManifestCustom: + type: object + properties: + type: + type: string + enum: + - custom + meta: + type: object + alias: + type: string + required: + - type + - alias + Manifest: + oneOf: + - $ref: '#/components/schemas/IManifestSection' + - $ref: '#/components/schemas/IManifestPropertyEditorUI' + - $ref: '#/components/schemas/IManifestDashboard' + - $ref: '#/components/schemas/IManifestEditorView' + - $ref: '#/components/schemas/IManifestPropertyAction' + - $ref: '#/components/schemas/IManifestEntrypoint' + - $ref: '#/components/schemas/IManifestCustom' + discriminator: + propertyName: type + mapping: + section: '#/components/schemas/IManifestSection' + propertyEditorUI: '#/components/schemas/IManifestPropertyEditorUI' + dashboard: '#/components/schemas/IManifestDashboard' + editorView: '#/components/schemas/IManifestEditorView' + propertyAction: '#/components/schemas/IManifestPropertyAction' + entrypoint: '#/components/schemas/IManifestEntrypoint' + custom: '#/components/schemas/IManifestCustom' + ManifestsResponse: + type: object + properties: + manifests: + type: array + items: + $ref: '#/components/schemas/Manifest' + required: + - manifests ServerStatus: type: string enum: diff --git a/src/Umbraco.Web.UI.Client/schemas/generated-schema.ts b/src/Umbraco.Web.UI.Client/schemas/generated-schema.ts index 4c9f3aa57f..add5f409e3 100644 --- a/src/Umbraco.Web.UI.Client/schemas/generated-schema.ts +++ b/src/Umbraco.Web.UI.Client/schemas/generated-schema.ts @@ -13,6 +13,9 @@ export interface paths { "/install/validateDatabase": { post: operations["PostInstallValidateDatabase"]; }; + "/manifests": { + get: operations["Manifests"]; + }; "/server/status": { get: operations["GetStatus"]; }; @@ -102,6 +105,100 @@ export interface components { telemetryLevel: components["schemas"]["ConsentLevel"]; database?: components["schemas"]["InstallSetupDatabaseConfiguration"]; }; + MetaSection: { + pathname: string; + /** Format: float */ + weight: number; + }; + IManifestSection: { + /** @enum {string} */ + type: "section"; + meta: components["schemas"]["MetaSection"]; + name: string; + js?: string; + elementName?: string; + alias: string; + }; + MetaPropertyEditorUI: { + icon: string; + group: string; + }; + IManifestPropertyEditorUI: { + /** @enum {string} */ + type: "propertyEditorUI"; + meta: components["schemas"]["MetaPropertyEditorUI"]; + name: string; + js?: string; + elementName?: string; + alias: string; + }; + MetaDashboard: { + sections: string[]; + pathname: string; + /** Format: float */ + weight: number; + label?: string; + }; + IManifestDashboard: { + /** @enum {string} */ + type: "dashboard"; + meta: components["schemas"]["MetaDashboard"]; + name: string; + js?: string; + elementName?: string; + alias: string; + }; + MetaEditorView: { + editors: string[]; + pathname: string; + /** Format: float */ + weight: number; + icon: string; + }; + IManifestEditorView: { + /** @enum {string} */ + type: "editorView"; + meta: components["schemas"]["MetaEditorView"]; + name: string; + js?: string; + elementName?: string; + alias: string; + }; + MetaPropertyAction: { + propertyEditors: string[]; + }; + IManifestPropertyAction: { + /** @enum {string} */ + type: "propertyAction"; + meta: components["schemas"]["MetaPropertyAction"]; + name: string; + js?: string; + elementName?: string; + alias: string; + }; + IManifestEntrypoint: { + /** @enum {string} */ + type: "entrypoint"; + js: string; + alias: string; + }; + IManifestCustom: { + /** @enum {string} */ + type: "custom"; + meta?: { [key: string]: unknown }; + alias: string; + }; + Manifest: + | components["schemas"]["IManifestSection"] + | components["schemas"]["IManifestPropertyEditorUI"] + | components["schemas"]["IManifestDashboard"] + | components["schemas"]["IManifestEditorView"] + | components["schemas"]["IManifestPropertyAction"] + | components["schemas"]["IManifestEntrypoint"] + | components["schemas"]["IManifestCustom"]; + ManifestsResponse: { + manifests: components["schemas"]["Manifest"][]; + }; /** @enum {string} */ ServerStatus: "running" | "must-install" | "must-upgrade"; StatusResponse: { @@ -185,6 +282,22 @@ export interface operations { }; }; }; + Manifests: { + responses: { + /** 200 response */ + 200: { + content: { + "application/json": components["schemas"]["ManifestsResponse"]; + }; + }; + /** default response */ + default: { + content: { + "application/json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; GetStatus: { responses: { /** 200 response */ diff --git a/src/Umbraco.Web.UI.Client/src/app.ts b/src/Umbraco.Web.UI.Client/src/app.ts index 093a567342..02f80c23d8 100644 --- a/src/Umbraco.Web.UI.Client/src/app.ts +++ b/src/Umbraco.Web.UI.Client/src/app.ts @@ -5,13 +5,13 @@ import { UUIIconRegistryEssential } from '@umbraco-ui/uui'; import { css, html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { getServerStatus } from './core/api/fetcher'; +import { getManifests, getServerStatus } from './core/api/fetcher'; import { UmbContextProviderMixin } from './core/context'; -import { UmbExtensionManifest, UmbExtensionManifestCore, UmbExtensionRegistry } from './core/extension'; +import { UmbExtensionRegistry } from './core/extension'; import { internalManifests } from './temp-internal-manifests'; -import type { Guard, IRoute } from 'router-slot/model'; import type { ServerStatus } from './core/models'; +import type { Guard, IRoute } from 'router-slot/model'; @customElement('umb-app') export class UmbApp extends UmbContextProviderMixin(LitElement) { @@ -121,17 +121,14 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) { } private async _registerExtensionManifestsFromServer() { - // TODO: add schema and use fetcher - const res = await fetch('/umbraco/backoffice/manifests'); - const { manifests } = await res.json(); - manifests.forEach((manifest: UmbExtensionManifest) => this._extensionRegistry.register(manifest)); + const res = await getManifests({}); + const { manifests } = res.data; + manifests.forEach((manifest) => this._extensionRegistry.register(manifest)); } private async _registerInternalManifests() { // TODO: where do we get these from? - internalManifests.forEach((manifest: UmbExtensionManifestCore) => - this._extensionRegistry.register(manifest) - ); + internalManifests.forEach((manifest) => this._extensionRegistry.register(manifest)); } render() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/components/backoffice-header-sections.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/components/backoffice-header-sections.element.ts index bbdd020854..eaaef3a9a0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/components/backoffice-header-sections.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/components/backoffice-header-sections.element.ts @@ -2,13 +2,11 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, CSSResultGroup, html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; -import { isPathActive, path } from 'router-slot'; import { Subscription } from 'rxjs'; -import { UmbContextConsumerMixin, UmbContextProvider, UmbContextProviderMixin } from '../../core/context'; -import { UmbExtensionManifestSection } from '../../core/extension'; +import { UmbContextConsumerMixin, UmbContextProviderMixin } from '../../core/context'; +import type { ManifestSection } from '../../core/models'; import { UmbSectionStore } from '../../core/stores/section.store'; -import { UmbSectionContext } from '../sections/section.context'; @customElement('umb-backoffice-header-sections') export class UmbBackofficeHeaderSections extends UmbContextProviderMixin(UmbContextConsumerMixin(LitElement)) { @@ -42,13 +40,13 @@ export class UmbBackofficeHeaderSections extends UmbContextProviderMixin(UmbCont private _open = false; @state() - private _sections: Array = []; + private _sections: Array = []; @state() - private _visibleSections: Array = []; + private _visibleSections: Array = []; @state() - private _extraSections: Array = []; + private _extraSections: Array = []; @state() private _currentSectionAlias = ''; @@ -117,7 +115,7 @@ export class UmbBackofficeHeaderSections extends UmbContextProviderMixin(UmbCont return html` ${this._visibleSections.map( - (section: UmbExtensionManifestSection) => html` + (section: ManifestSection) => html` = []; @state() - private _sections: Array = []; + private _sections: Array = []; private _routePrefix = 'section/'; private _sectionContext?: UmbSectionContext; @@ -79,7 +80,7 @@ export class UmbBackofficeMain extends UmbContextProviderMixin(UmbContextConsume this._provideSectionContext(section); }; - private _provideSectionContext(section: UmbExtensionManifestSection) { + private _provideSectionContext(section: ManifestSection) { if (!this._sectionContext) { this._sectionContext = new UmbSectionContext(section); this.provideContext('umbSectionContext', this._sectionContext); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/components/node-property.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/components/node-property.element.ts index b1cdcd8f54..7c16c3f09f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/components/node-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/components/node-property.element.ts @@ -1,14 +1,16 @@ +import '../property-actions/property-action-menu/property-action-menu.element'; + import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, LitElement, PropertyValueMap } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { distinctUntilChanged, EMPTY, of, Subscription, switchMap } from 'rxjs'; + import { UmbContextConsumerMixin } from '../../core/context'; -import { createExtensionElement, UmbExtensionManifest, UmbExtensionRegistry } from '../../core/extension'; +import { createExtensionElement, UmbExtensionRegistry } from '../../core/extension'; +import type { ManifestPropertyEditorUI } from '../../core/models'; import { UmbDataTypeStore } from '../../core/stores/data-type.store'; import { DataTypeEntity } from '../../mocks/data/data-type.data'; -import '../property-actions/property-action-menu/property-action-menu.element'; - @customElement('umb-node-property') class UmbNodeProperty extends UmbContextConsumerMixin(LitElement) { static styles = [ @@ -94,16 +96,16 @@ class UmbNodeProperty extends UmbContextConsumerMixin(LitElement) { return this._extensionRegistry?.getByAlias(dataTypeEntity.propertyEditorUIAlias) ?? of(null); }) ) - .subscribe((propertyEditorUI) => { - if (propertyEditorUI) { - this._gotData(propertyEditorUI); + .subscribe((extension) => { + if (extension?.type === 'propertyEditorUI') { + this._gotData(extension); } // TODO: If gone what then... }); } } - private _gotData(_propertyEditorUI?: UmbExtensionManifest) { + private _gotData(_propertyEditorUI?: ManifestPropertyEditorUI) { if (!this._dataType || !_propertyEditorUI) { // TODO: if dataTypeKey didn't exist in store, we should do some nice UI. return; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/dashboards/redirect-management/dashboard-redirect-management.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/dashboards/redirect-management/dashboard-redirect-management.stories.ts index c492f69ee4..99d180a303 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/dashboards/redirect-management/dashboard-redirect-management.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/dashboards/redirect-management/dashboard-redirect-management.stories.ts @@ -1,8 +1,9 @@ +import './dashboard-redirect-management.element'; + import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit-html'; -import { UmbDashboardRedirectManagementElement } from './dashboard-redirect-management.element'; -import './dashboard-redirect-management.element'; +import type { UmbDashboardRedirectManagementElement } from './dashboard-redirect-management.element'; export default { title: 'Dashboards/Redirect Management', diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/dashboards/welcome/dashboard-welcome.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/dashboards/welcome/dashboard-welcome.stories.ts index 42028e5030..82ad05aea5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/dashboards/welcome/dashboard-welcome.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/dashboards/welcome/dashboard-welcome.stories.ts @@ -1,8 +1,9 @@ +import './dashboard-welcome.element'; + import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit-html'; -import { UmbDashboardWelcomeElement } from './dashboard-welcome.element'; -import './dashboard-welcome.element'; +import type { UmbDashboardWelcomeElement } from './dashboard-welcome.element'; export default { title: 'Dashboards/Welcome', diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/content/editor-content.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/content/editor-content.stories.ts index aebb0076f3..0dc1864bab 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/content/editor-content.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/content/editor-content.stories.ts @@ -1,11 +1,12 @@ +import './editor-content.element'; + import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit-html'; -import { UmbEditorContentElement } from './editor-content.element'; -import './editor-content.element'; - import { data } from '../../../mocks/data/node.data'; +import type { UmbEditorContentElement } from './editor-content.element'; + export default { title: 'Editors/Content', component: 'umb-editor-content', diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/editor-data-type.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/editor-data-type.stories.ts index cdb79a4095..6e3e210769 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/editor-data-type.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/editor-data-type.stories.ts @@ -1,11 +1,12 @@ +import './editor-data-type.element'; + import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit-html'; -import { UmbEditorDataTypeElement } from './editor-data-type.element'; -import './editor-data-type.element'; - import { data } from '../../../mocks/data/data-type.data'; +import type { UmbEditorDataTypeElement } from './editor-data-type.element'; + export default { title: 'Editors/Data Type', component: 'umb-editor-data-type', diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/views/editor-view-data-type-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/views/editor-view-data-type-edit.element.ts index 5fa870bdb6..916a6af190 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/views/editor-view-data-type-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/views/editor-view-data-type-edit.element.ts @@ -1,14 +1,16 @@ -import { css, html, LitElement } from 'lit'; +import { UUIComboboxListElement, UUIComboboxListEvent } from '@umbraco-ui/uui'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { css, html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; -import { UmbContextConsumerMixin } from '../../../../core/context'; -import type { DataTypeEntity } from '../../../../mocks/data/data-type.data'; -import type { UmbExtensionManifestPropertyEditorUI, UmbExtensionRegistry } from '../../../../core/extension'; -import { Subscription, distinctUntilChanged } from 'rxjs'; -import { UmbDataTypeContext } from '../data-type.context'; -import { UUIComboboxListElement, UUIComboboxListEvent } from '@umbraco-ui/uui'; +import { distinctUntilChanged, Subscription } from 'rxjs'; +import { UmbContextConsumerMixin } from '../../../../core/context'; +import type { ManifestPropertyEditorUI } from '../../../../core/models'; +import { UmbDataTypeContext } from '../data-type.context'; + +import type { DataTypeEntity } from '../../../../mocks/data/data-type.data'; +import type { UmbExtensionRegistry } from '../../../../core/extension'; @customElement('umb-editor-view-data-type-edit') export class UmbEditorViewDataTypeEditElement extends UmbContextConsumerMixin(LitElement) { static styles = [UUITextStyles, css``]; @@ -17,7 +19,7 @@ export class UmbEditorViewDataTypeEditElement extends UmbContextConsumerMixin(Li _dataType?: DataTypeEntity; @state() - private _propertyEditorUIs: Array = []; + private _propertyEditorUIs: Array = []; private _extensionRegistry?: UmbExtensionRegistry; private _dataTypeContext?: UmbDataTypeContext; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/views/editor-view-data-type-edit.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/views/editor-view-data-type-edit.stories.ts index 2a7cfb1ae7..ef68c2ec7c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/views/editor-view-data-type-edit.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/views/editor-view-data-type-edit.stories.ts @@ -1,10 +1,12 @@ +import './editor-view-data-type-edit.element'; + import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit-html'; -import { UmbDataTypeContext } from '../data-type.context'; -import { UmbEditorViewDataTypeEditElement } from './editor-view-data-type-edit.element'; -import './editor-view-data-type-edit.element'; import { data } from '../../../../mocks/data/data-type.data'; +import { UmbDataTypeContext } from '../data-type.context'; + +import type { UmbEditorViewDataTypeEditElement } from './editor-view-data-type-edit.element'; export default { title: 'Editors/Data Type/Views/Edit', diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/editor-document-type.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/editor-document-type.stories.ts index ea9a5f50f4..787b83c706 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/editor-document-type.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/editor-document-type.stories.ts @@ -1,11 +1,12 @@ +import './editor-document-type.element'; + import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit-html'; -import { UmbEditorDocumentTypeElement } from './editor-document-type.element'; -import './editor-document-type.element'; - import { data } from '../../../mocks/data/document-type.data'; +import type { UmbEditorDocumentTypeElement } from './editor-document-type.element'; + export default { title: 'Editors/Document Type', component: 'umb-editor-document-type', diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/views/editor-view-document-type-design.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/views/editor-view-document-type-design.stories.ts index cd747c26ce..1ff23e754d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/views/editor-view-document-type-design.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/views/editor-view-document-type-design.stories.ts @@ -1,10 +1,12 @@ +import './editor-view-document-type-design.element'; + import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit-html'; -import { UmbDocumentTypeContext } from '../document-type.context'; -import { UmbEditorViewDocumentTypeDesignElement } from './editor-view-document-type-design.element'; -import './editor-view-document-type-design.element'; import { data } from '../../../../mocks/data/document-type.data'; +import { UmbDocumentTypeContext } from '../document-type.context'; + +import type { UmbEditorViewDocumentTypeDesignElement } from './editor-view-document-type-design.element'; export default { title: 'Editors/Document Type/Views/Design', diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/extensions/editor-extensions.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/extensions/editor-extensions.element.ts index de709ca97b..1f1950d6a3 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/extensions/editor-extensions.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/extensions/editor-extensions.element.ts @@ -1,15 +1,19 @@ +import '../shared/editor-entity/editor-entity.element'; + import { html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { Subscription } from 'rxjs'; -import { UmbContextConsumerMixin } from '../../../core/context'; -import { UmbExtensionManifest, UmbExtensionRegistry } from '../../../core/extension'; -import '../shared/editor-entity/editor-entity.element'; +import { UmbContextConsumerMixin } from '../../../core/context'; +import { UmbExtensionRegistry } from '../../../core/extension'; +import { isManifestElementType } from '../../../core/extension/is-extension.function'; + +import type { ManifestTypes } from '../../../core/models'; @customElement('umb-editor-extensions') export class UmbEditorExtensionsElement extends UmbContextConsumerMixin(LitElement) { @state() - private _extensions: Array = []; + private _extensions: Array = []; private _extensionRegistry?: UmbExtensionRegistry; private _extensionsSubscription?: Subscription; @@ -62,7 +66,9 @@ export class UmbEditorExtensionsElement extends UmbContextConsumerMixin(LitEleme (extension) => html` ${extension.type} - ${extension.name} + + ${isManifestElementType(extension) ? extension.name : 'Custom extension'} + ${extension.alias} ` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/extensions/editor-extensions.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/extensions/editor-extensions.stories.ts index d084a04646..a6d35c21fb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/extensions/editor-extensions.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/extensions/editor-extensions.stories.ts @@ -1,8 +1,9 @@ +import './editor-extensions.element'; + import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit-html'; -import { UmbEditorExtensionsElement } from './editor-extensions.element'; -import './editor-extensions.element'; +import type { UmbEditorExtensionsElement } from './editor-extensions.element'; export default { title: 'Editors/Extensions', diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/media/editor-media.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/media/editor-media.stories.ts index d036b1cf53..62fb15dbf3 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/media/editor-media.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/media/editor-media.stories.ts @@ -1,11 +1,12 @@ +import './editor-media.element'; + import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit-html'; -import { UmbEditorMediaElement } from './editor-media.element'; -import './editor-media.element'; - import { data } from '../../../mocks/data/node.data'; +import type { UmbEditorMediaElement } from './editor-media.element'; + export default { title: 'Editors/Media', component: 'umb-editor-media', diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/shared/editor-entity/editor-entity.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/shared/editor-entity/editor-entity.element.ts index 8d52e1095c..8a2220e081 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/shared/editor-entity/editor-entity.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/shared/editor-entity/editor-entity.element.ts @@ -1,12 +1,15 @@ -import { css, html, LitElement, nothing } from 'lit'; -import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, property, state } from 'lit/decorators.js'; -import { UmbContextConsumerMixin } from '../../../../core/context'; -import { UmbExtensionManifestEditorView, UmbExtensionRegistry } from '../../../../core/extension'; -import { map, Subscription } from 'rxjs'; -import { IRoute, IRoutingInfo, RouterSlot } from 'router-slot'; - import '../editor-layout/editor-layout.element'; + +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { css, html, LitElement, nothing } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { IRoute, IRoutingInfo, RouterSlot } from 'router-slot'; +import { map, Subscription } from 'rxjs'; + +import { UmbContextConsumerMixin } from '../../../../core/context'; +import { UmbExtensionRegistry } from '../../../../core/extension'; +import type { ManifestEditorView } from '../../../../core/models'; + @customElement('umb-editor-entity') export class UmbEditorEntity extends UmbContextConsumerMixin(LitElement) { static styles = [ @@ -62,7 +65,7 @@ export class UmbEditorEntity extends UmbContextConsumerMixin(LitElement) { name = ''; @state() - private _editorViews: Array = []; + private _editorViews: Array = []; @state() private _currentView = ''; @@ -147,7 +150,7 @@ export class UmbEditorEntity extends UmbContextConsumerMixin(LitElement) { ? html` ${this._editorViews.map( - (view: UmbExtensionManifestEditorView) => html` + (view: ManifestEditorView) => html` = []; + private _actions: Array = []; @state() private _open = false; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/property-actions/property-action/property-action.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/property-actions/property-action/property-action.element.ts index ecef8fa1ac..75d5c7d73b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/property-actions/property-action/property-action.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/property-actions/property-action/property-action.element.ts @@ -1,19 +1,22 @@ import { UUITextStyles } from '@umbraco-ui/uui'; import { CSSResultGroup, html, LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { createExtensionElement, UmbExtensionManifestPropertyAction } from '../../../core/extension'; + +import { createExtensionElement } from '../../../core/extension'; +import type { ManifestPropertyAction } from '../../../core/models'; + import type { UmbPropertyAction } from './property-action.model'; @customElement('umb-property-action') export class UmbPropertyActionElement extends LitElement implements UmbPropertyAction { static styles: CSSResultGroup = [UUITextStyles]; - private _propertyAction?: UmbExtensionManifestPropertyAction; + private _propertyAction?: ManifestPropertyAction; @property({ type: Object }) - public get propertyAction(): UmbExtensionManifestPropertyAction | undefined { + public get propertyAction(): ManifestPropertyAction | undefined { return this._propertyAction; } - public set propertyAction(value: UmbExtensionManifestPropertyAction | undefined) { + public set propertyAction(value: ManifestPropertyAction | undefined) { this._propertyAction = value; this._createElement(); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/sections/section.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/sections/section.context.ts index e783942757..8b5d8b64ed 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/sections/section.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/sections/section.context.ts @@ -1,26 +1,29 @@ -import { BehaviorSubject, Observable } from 'rxjs'; -import { UmbExtensionManifestSection } from '../../core/extension'; +import { BehaviorSubject } from 'rxjs'; + +import type { ManifestSection } from '../../core/models'; export class UmbSectionContext { // TODO: figure out how fine grained we want to make our observables. - private _data: BehaviorSubject = new BehaviorSubject({ + private _data = new BehaviorSubject({ type: 'section', alias: '', name: '', + js: '', + elementName: '', meta: { pathname: '', weight: 0, }, }); - public readonly data: Observable = this._data.asObservable(); + public readonly data = this._data.asObservable(); - constructor(section: UmbExtensionManifestSection) { + constructor(section: ManifestSection) { if (!section) return; this._data.next(section); } // TODO: figure out how we want to update data - public update(data: Partial) { + public update(data: Partial) { this._data.next({ ...this._data.getValue(), ...data }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/sections/shared/section-dashboards.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/sections/shared/section-dashboards.element.ts index d11a47cc2e..fd4662b525 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/sections/shared/section-dashboards.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/sections/shared/section-dashboards.element.ts @@ -2,12 +2,14 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, LitElement, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { IRoutingInfo } from 'router-slot'; -import { map, Subscription, first } from 'rxjs'; +import { first, map, Subscription } from 'rxjs'; import { UmbContextConsumerMixin } from '../../../core/context'; -import { createExtensionElement, UmbExtensionManifestDashboard, UmbExtensionRegistry } from '../../../core/extension'; +import { createExtensionElement, UmbExtensionRegistry } from '../../../core/extension'; import { UmbSectionContext } from '../section.context'; +import type { ManifestDashboard } from '../../../core/models'; + @customElement('umb-section-dashboards') export class UmbSectionDashboards extends UmbContextConsumerMixin(LitElement) { static styles = [ @@ -33,7 +35,7 @@ export class UmbSectionDashboards extends UmbContextConsumerMixin(LitElement) { ]; @state() - private _dashboards: Array = []; + private _dashboards: Array = []; @state() private _currentDashboardPathname = ''; @@ -104,7 +106,7 @@ export class UmbSectionDashboards extends UmbContextConsumerMixin(LitElement) { return { path: `${dashboard.meta.pathname}`, component: () => createExtensionElement(dashboard), - setup: (_element: UmbExtensionManifestDashboard, info: IRoutingInfo) => { + setup: (_element: ManifestDashboard, info: IRoutingInfo) => { this._currentDashboardPathname = info.match.route.path; }, }; @@ -122,7 +124,7 @@ export class UmbSectionDashboards extends UmbContextConsumerMixin(LitElement) { ? html` ${this._dashboards.map( - (dashboard: UmbExtensionManifestDashboard) => html` + (dashboard) => html` (); @@ -19,3 +19,4 @@ export const postInstallValidateDatabase = fetcher.path('/install/validateDataba export const postInstallSetup = fetcher.path('/install/setup').method('post').create(); export const getUpgradeSettings = fetcher.path('/upgrade/settings').method('get').create(); export const PostUpgradeAuthorize = fetcher.path('/upgrade/authorize').method('post').create(); +export const getManifests = fetcher.path('/manifests').method('get').create(); diff --git a/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.mixin.ts index 4162344031..5e639f9898 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.mixin.ts @@ -1,4 +1,4 @@ -import { HTMLElementConstructor } from '../models'; +import type { HTMLElementConstructor } from '../models'; import { UmbContextConsumer } from './context-consumer'; export declare class UmbContextConsumerInterface { diff --git a/src/Umbraco.Web.UI.Client/src/core/context/context-provider.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/context/context-provider.mixin.ts index 26a4c95e66..a8784e091e 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context/context-provider.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context/context-provider.mixin.ts @@ -1,4 +1,4 @@ -import { HTMLElementConstructor } from '../models'; +import type { HTMLElementConstructor } from '../models'; import { UmbContextProvider } from './context-provider'; export declare class UmbContextProviderMixinInterface { diff --git a/src/Umbraco.Web.UI.Client/src/core/extension/create-extension-element.function.ts b/src/Umbraco.Web.UI.Client/src/core/extension/create-extension-element.function.ts index 4b2f261060..354f44be0c 100644 --- a/src/Umbraco.Web.UI.Client/src/core/extension/create-extension-element.function.ts +++ b/src/Umbraco.Web.UI.Client/src/core/extension/create-extension-element.function.ts @@ -1,29 +1,29 @@ -import { UmbExtensionManifest } from './extension.registry'; import { hasDefaultExport } from './has-default-export.function'; -import { isExtensionType } from './is-extension.function'; +import { isManifestElementType } from './is-extension.function'; import { loadExtension } from './load-extension.function'; -export async function createExtensionElement(manifest: UmbExtensionManifest): Promise { +import type { ManifestTypes } from '../models'; + +export async function createExtensionElement(manifest: ManifestTypes): Promise { //TODO: Write tests for these extension options: const js = await loadExtension(manifest); - if (manifest.elementName) { + + if (isManifestElementType(manifest) && manifest.elementName) { // created by manifest method providing HTMLElement return document.createElement(manifest.elementName); } + + // TODO: Do we need this except for the default() loader? if (js) { - if (js instanceof HTMLElement) { - console.log('-- created by manifest method providing HTMLElement', js); - return js; - } - if (isExtensionType(js)) { - // created by js export elementName - return js.elementName ? document.createElement(js.elementName) : Promise.resolve(undefined); - } if (hasDefaultExport(js)) { // created by default class return new js.default(); } + + // If some JS was loaded and it did not at least contain a default export, then we are safe to assume that it executed as a module and does not need to be returned + return undefined; } - console.error('-- Extension did not succeed creating an element'); - return Promise.resolve(undefined); + + console.error('-- Extension did not succeed creating an element', manifest); + return undefined; } diff --git a/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts b/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts index 815853d89f..f830563458 100644 --- a/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts +++ b/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts @@ -1,100 +1,22 @@ import { BehaviorSubject, map, Observable } from 'rxjs'; -export type UmbExtensionManifestJSModel = { - elementName?: string; -}; - -export type UmbExtensionManifestBase = { - //type: string; - alias: string; - name: string; - js?: string | (() => Promise); - elementName?: string; - //meta: undefined; -}; - -// Core manifest types: - -// Section: -export type UmbManifestSectionMeta = { - pathname: string; // TODO: how to we want to support pretty urls? - weight: number; -}; -export type UmbExtensionManifestSection = { - type: 'section'; - meta: UmbManifestSectionMeta; -} & UmbExtensionManifestBase; - -// propertyEditor: -export type UmbManifestPropertyEditorMeta = { - icon: string; - group: string; // TODO: use group alias or other name to indicate that it could be used to look up translation. - //groupAlias: string; - //description: string; - //configConfig: unknown; // we need a name and concept for how to setup editor-UI for -}; -export type UmbExtensionManifestPropertyEditorUI = { - type: 'propertyEditorUI'; - meta: UmbManifestPropertyEditorMeta; -} & UmbExtensionManifestBase; - -// Property Actions -export type UmbExtensionManifestPropertyAction = { - type: 'propertyAction'; - meta: UmbManifestPropertyActionMeta; -} & UmbExtensionManifestBase; - -export type UmbManifestPropertyActionMeta = { - propertyEditors: Array; -}; - -// Dashboard: -export type UmbManifestDashboardMeta = { - sections: Array; - label?: string; - pathname: string; // TODO: how to we want to support pretty urls? - weight: number; -}; -export type UmbExtensionManifestDashboard = { - type: 'dashboard'; - meta: UmbManifestDashboardMeta; -} & UmbExtensionManifestBase; - -// Editor View: -export type UmbManifestEditorViewMeta = { - editors: Array; // TODO: how to we want to filter views? - pathname: string; // TODO: how to we want to support pretty urls? - icon: string; - weight: number; -}; -export type UmbExtensionManifestEditorView = { - type: 'editorView'; - meta: UmbManifestEditorViewMeta; -} & UmbExtensionManifestBase; - -export type UmbExtensionManifestCore = - | UmbExtensionManifestSection - | UmbExtensionManifestDashboard - | UmbExtensionManifestPropertyEditorUI - | UmbExtensionManifestPropertyAction - | UmbExtensionManifestEditorView; - -// the 'Other' manifest type: - -type UmbExtensionManifestOther = { - type: string; - meta: unknown; -} & UmbExtensionManifestBase; - -export type UmbExtensionManifest = UmbExtensionManifestCore | UmbExtensionManifestOther; - -type UmbExtensionManifestCoreTypes = Pick['type']; +import { createExtensionElement } from './create-extension-element.function'; +import type { + ManifestTypes, + ManifestDashboard, + ManifestEditorView, + ManifestEntrypoint, + ManifestPropertyAction, + ManifestPropertyEditorUI, + ManifestSection, + ManifestCustom, +} from '../models'; export class UmbExtensionRegistry { - private _extensions = new BehaviorSubject>([]); + private _extensions = new BehaviorSubject>([]); public readonly extensions = this._extensions.asObservable(); - register(manifest: T): void { + register(manifest: ManifestTypes): void { const extensionsValues = this._extensions.getValue(); const extension = extensionsValues.find((extension) => extension.alias === manifest.alias); @@ -104,9 +26,15 @@ export class UmbExtensionRegistry { } this._extensions.next([...extensionsValues, manifest]); + + // If entrypoint extension, we should load it immediately + if (manifest.type === 'entrypoint') { + createExtensionElement(manifest); + } } - getByAlias(alias: string): Observable { + getByAlias(alias: string): Observable; + getByAlias(alias: string) { // TODO: make pipes prettier/simpler/reuseable return this.extensions.pipe(map((dataTypes) => dataTypes.find((extension) => extension.alias === alias) || null)); } @@ -114,15 +42,15 @@ export class UmbExtensionRegistry { // TODO: implement unregister of extension // Typings concept, need to put all core types to get a good array return type for the provided type... - extensionsOfType(type: 'section'): Observable>; - extensionsOfType(type: 'dashboard'): Observable>; - extensionsOfType(type: 'editorView'): Observable>; - extensionsOfType(type: 'propertyEditorUI'): Observable>; - extensionsOfType(type: 'propertyAction'): Observable>; - extensionsOfType(type: UmbExtensionManifestCoreTypes): Observable>; - extensionsOfType(type: string): Observable>; - extensionsOfType(type: string): Observable>; - extensionsOfType(type: string) { + extensionsOfType(type: 'section'): Observable>; + extensionsOfType(type: 'dashboard'): Observable>; + extensionsOfType(type: 'editorView'): Observable>; + extensionsOfType(type: 'propertyEditorUI'): Observable>; + extensionsOfType(type: 'propertyAction'): Observable>; + extensionsOfType(type: 'entrypoint'): Observable>; + extensionsOfType(type: 'custom'): Observable>; + extensionsOfType(type: string): Observable>; + extensionsOfType(type: string): Observable> { return this.extensions.pipe(map((exts) => exts.filter((ext) => ext.type === type))); } } diff --git a/src/Umbraco.Web.UI.Client/src/core/extension/has-default-export.function.ts b/src/Umbraco.Web.UI.Client/src/core/extension/has-default-export.function.ts index d8b965ced5..de21ac631b 100644 --- a/src/Umbraco.Web.UI.Client/src/core/extension/has-default-export.function.ts +++ b/src/Umbraco.Web.UI.Client/src/core/extension/has-default-export.function.ts @@ -1,4 +1,4 @@ -import { HTMLElementConstructor } from '../models'; +import type { HTMLElementConstructor } from '../models'; export function hasDefaultExport(object: unknown): object is { default: HTMLElementConstructor } { return typeof object === 'object' && object !== null && 'default' in object; diff --git a/src/Umbraco.Web.UI.Client/src/core/extension/is-extension.function.ts b/src/Umbraco.Web.UI.Client/src/core/extension/is-extension.function.ts index 93cdabdfee..b6998ae8d8 100644 --- a/src/Umbraco.Web.UI.Client/src/core/extension/is-extension.function.ts +++ b/src/Umbraco.Web.UI.Client/src/core/extension/is-extension.function.ts @@ -1,9 +1,7 @@ -import { UmbExtensionManifestBase } from './extension.registry'; +import type { ManifestElementType } from '../models'; -export function isExtensionType(manifest: unknown): manifest is UmbExtensionManifestBase { +export function isManifestElementType(manifest: unknown): manifest is ManifestElementType { return ( - typeof manifest === 'object' && - manifest !== null && - (manifest as UmbExtensionManifestBase).elementName !== undefined + typeof manifest === 'object' && manifest !== null && (manifest as ManifestElementType).elementName !== undefined ); } diff --git a/src/Umbraco.Web.UI.Client/src/core/extension/load-extension.function.ts b/src/Umbraco.Web.UI.Client/src/core/extension/load-extension.function.ts index 73202c1a65..969d97aaef 100644 --- a/src/Umbraco.Web.UI.Client/src/core/extension/load-extension.function.ts +++ b/src/Umbraco.Web.UI.Client/src/core/extension/load-extension.function.ts @@ -1,31 +1,29 @@ -import { UmbExtensionManifest } from './extension.registry'; +import type { ManifestTypes } from '../models'; -export function loadExtension(manifest: UmbExtensionManifest): Promise | Promise { - if (typeof manifest.js === 'function') { - return manifest.js() as Promise; +export type ManifestLoaderType = ManifestTypes & { loader: () => Promise }; +export type ManifestJSType = ManifestTypes & { js: string }; + +export async function loadExtension(manifest: ManifestTypes): Promise { + try { + if (isManifestLoaderType(manifest)) { + return manifest.loader(); + } + + if (isManifestJSType(manifest) && manifest.js) { + return await import(/* @vite-ignore */ manifest.js); + } + } catch { + console.warn('-- Extension failed to load script', manifest); + return Promise.resolve(null); } - // TODO: verify if this is acceptable solution. - if (typeof manifest.js === 'string') { - // TODO: change this back to dynamic import after POC. Vite complains about the dynamic imports in the public folder but doesn't include the temp app_plugins folder in the final build. - // return import(/* @vite-ignore */ manifest.js); - return new Promise((resolve, reject) => { - const script = document.createElement('script'); - script.type = 'text/javascript'; - //script.charset = 'utf-8'; - script.async = true; - script.type = 'module'; - script.src = manifest.js as string; - script.onload = function () { - resolve(null); - }; - script.onerror = function () { - reject(new Error(`Script load error for ${manifest.js}`)); - }; - document.body.appendChild(script); - }) as Promise; - } - - console.log('-- Extension does not have any referenced JS'); return Promise.resolve(null); } + +export function isManifestLoaderType(manifest: ManifestTypes): manifest is ManifestLoaderType { + return typeof (manifest as ManifestLoaderType).loader === 'function'; +} + +export function isManifestJSType(manifest: ManifestTypes): manifest is ManifestJSType { + return (manifest as ManifestJSType).js !== undefined; +} diff --git a/src/Umbraco.Web.UI.Client/src/core/helpers/umbraco-path.ts b/src/Umbraco.Web.UI.Client/src/core/helpers/umbraco-path.ts index 89c92816cb..f77f30c645 100644 --- a/src/Umbraco.Web.UI.Client/src/core/helpers/umbraco-path.ts +++ b/src/Umbraco.Web.UI.Client/src/core/helpers/umbraco-path.ts @@ -1,6 +1,6 @@ import { Path } from 'msw'; -import { paths } from '../../../schemas/generated-schema'; +import type { paths } from '../../../schemas/generated-schema'; export default function umbracoPath(path: keyof paths): Path { return `/umbraco/backoffice${path}`; diff --git a/src/Umbraco.Web.UI.Client/src/core/models/index.ts b/src/Umbraco.Web.UI.Client/src/core/models/index.ts index c13524064c..e479692ca9 100644 --- a/src/Umbraco.Web.UI.Client/src/core/models/index.ts +++ b/src/Umbraco.Web.UI.Client/src/core/models/index.ts @@ -1,4 +1,4 @@ -import { components } from '../../../schemas/generated-schema'; +import type { components } from '../../../schemas/generated-schema'; export type PostInstallRequest = components['schemas']['InstallSetupRequest']; export type StatusResponse = components['schemas']['StatusResponse']; @@ -8,6 +8,7 @@ export type UserResponse = components['schemas']['UserResponse']; export type AllowedSectionsResponse = components['schemas']['AllowedSectionsResponse']; export type UmbracoInstaller = components['schemas']['InstallSettingsResponse']; export type UmbracoUpgrader = components['schemas']['UpgradeSettingsResponse']; +export type ManifestsResponse = components['schemas']['ManifestsResponse']; // Models export type UmbracoPerformInstallDatabaseConfiguration = components['schemas']['InstallSetupDatabaseConfiguration']; @@ -15,6 +16,21 @@ export type UmbracoInstallerDatabaseModel = components['schemas']['InstallDataba export type UmbracoInstallerUserModel = components['schemas']['InstallUserModel']; export type TelemetryModel = components['schemas']['TelemetryModel']; export type ServerStatus = components['schemas']['ServerStatus']; +export type ManifestTypes = components['schemas']['Manifest']; +export type ManifestSection = components['schemas']['IManifestSection']; +export type ManifestPropertyEditorUI = components['schemas']['IManifestPropertyEditorUI']; +export type ManifestDashboard = components['schemas']['IManifestDashboard']; +export type ManifestEditorView = components['schemas']['IManifestEditorView']; +export type ManifestPropertyAction = components['schemas']['IManifestPropertyAction']; +export type ManifestEntrypoint = components['schemas']['IManifestEntrypoint']; +export type ManifestCustom = components['schemas']['IManifestCustom']; + +export type ManifestElementType = + | ManifestSection + | ManifestPropertyAction + | ManifestPropertyEditorUI + | ManifestDashboard + | ManifestEditorView; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type HTMLElementConstructor = new (...args: any[]) => T; diff --git a/src/Umbraco.Web.UI.Client/src/core/services/modal/layouts/confirm/modal-layout-confirm.stories.ts b/src/Umbraco.Web.UI.Client/src/core/services/modal/layouts/confirm/modal-layout-confirm.stories.ts index f1e3e1f715..93d8ba7c2a 100644 --- a/src/Umbraco.Web.UI.Client/src/core/services/modal/layouts/confirm/modal-layout-confirm.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/core/services/modal/layouts/confirm/modal-layout-confirm.stories.ts @@ -1,7 +1,9 @@ +import './modal-layout-confirm.element'; + import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit'; -import { UmbModalLayoutConfirmElement, UmbModalConfirmData } from './modal-layout-confirm.element'; -import './modal-layout-confirm.element'; + +import type { UmbModalLayoutConfirmElement, UmbModalConfirmData } from './modal-layout-confirm.element'; export default { title: 'API/Modals/Layouts/Confirm', @@ -17,7 +19,7 @@ const positiveData: UmbModalConfirmData = { }; export const Positive: Story = () => html` - @@ -31,7 +33,7 @@ const dangerData: UmbModalConfirmData = { }; export const Danger: Story = () => html` - diff --git a/src/Umbraco.Web.UI.Client/src/core/services/modal/layouts/content-picker/modal-layout-content-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/core/services/modal/layouts/content-picker/modal-layout-content-picker.stories.ts index 3422b78379..f74f928ddc 100644 --- a/src/Umbraco.Web.UI.Client/src/core/services/modal/layouts/content-picker/modal-layout-content-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/core/services/modal/layouts/content-picker/modal-layout-content-picker.stories.ts @@ -1,9 +1,13 @@ -import { Meta, Story } from '@storybook/web-components'; -import { html } from 'lit'; -import { UmbModalLayoutContentPickerElement, UmbModalContentPickerData } from './modal-layout-content-picker.element'; +import '../../../../../backoffice/editors/shared/editor-layout/editor-layout.element'; import './modal-layout-content-picker.element'; -import '../../../../../backoffice/editors/shared/editor-layout/editor-layout.element'; +import { Meta, Story } from '@storybook/web-components'; +import { html } from 'lit'; + +import type { + UmbModalLayoutContentPickerElement, + UmbModalContentPickerData, +} from './modal-layout-content-picker.element'; export default { title: 'API/Modals/Layouts/Content Picker', @@ -14,7 +18,7 @@ export default { const data: UmbModalContentPickerData = {}; export const Overview: Story = () => html` - diff --git a/src/Umbraco.Web.UI.Client/src/core/services/modal/modal.service.ts b/src/Umbraco.Web.UI.Client/src/core/services/modal/modal.service.ts index ac363efc3e..285078d136 100644 --- a/src/Umbraco.Web.UI.Client/src/core/services/modal/modal.service.ts +++ b/src/Umbraco.Web.UI.Client/src/core/services/modal/modal.service.ts @@ -1,13 +1,15 @@ -import { BehaviorSubject, Observable } from 'rxjs'; -import { UmbModalHandler } from './'; -import { UmbModalConfirmData } from './layouts/confirm/modal-layout-confirm.element'; -import { UmbModalContentPickerData } from './layouts/content-picker/modal-layout-content-picker.element'; -import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar'; - // TODO: lazy load import './layouts/confirm/modal-layout-confirm.element'; import './layouts/content-picker/modal-layout-content-picker.element'; +import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar'; +import { BehaviorSubject, Observable } from 'rxjs'; + +import { UmbModalHandler } from './'; + +import type { UmbModalConfirmData } from './layouts/confirm/modal-layout-confirm.element'; +import type { UmbModalContentPickerData } from './layouts/content-picker/modal-layout-content-picker.element'; + export type UmbModelType = 'dialog' | 'sidebar'; export interface UmbModalOptions { diff --git a/src/Umbraco.Web.UI.Client/src/core/services/modal/modal.stories.ts b/src/Umbraco.Web.UI.Client/src/core/services/modal/modal.stories.ts index f2e1f77107..2677b3008f 100644 --- a/src/Umbraco.Web.UI.Client/src/core/services/modal/modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/core/services/modal/modal.stories.ts @@ -1,18 +1,18 @@ import '../../../backoffice/components/backoffice-modal-container.element'; +import '../../../backoffice/editors/shared/editor-layout/editor-layout.element'; import '../../../core/services/modal/layouts/content-picker/modal-layout-content-picker.element'; import '../../context/context-provider.element'; -import '../../../backoffice/editors/shared/editor-layout/editor-layout.element'; - import '@umbraco-ui/uui-modal'; import '@umbraco-ui/uui-modal-container'; -import '@umbraco-ui/uui-modal-sidebar'; import '@umbraco-ui/uui-modal-dialog'; +import '@umbraco-ui/uui-modal-sidebar'; 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 { UmbContextConsumerMixin } from '../../context'; -import { LitElement } from 'lit'; import { UmbModalService } from './'; export default { @@ -34,7 +34,7 @@ export default { } as Meta; @customElement('story-modal-service-example') -class StoryModalServiceExampleElement extends UmbContextConsumerMixin(LitElement) { +export class StoryModalServiceExampleElement extends UmbContextConsumerMixin(LitElement) { @property() modalLayout = 'confirm'; diff --git a/src/Umbraco.Web.UI.Client/src/core/services/notification/notification.stories.ts b/src/Umbraco.Web.UI.Client/src/core/services/notification/notification.stories.ts index 157787db0e..670ac5fa55 100644 --- a/src/Umbraco.Web.UI.Client/src/core/services/notification/notification.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/core/services/notification/notification.stories.ts @@ -1,14 +1,15 @@ -import { Meta, Story } from '@storybook/web-components'; -import { LitElement, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; -import { UmbNotificationService, UmbNotificationOptions, UmbNotificationColor } from '.'; -import type { UmbNotificationDefaultData } from './layouts/default'; -import { UmbContextConsumerMixin } from '../../context'; - -import '../../context/context-provider.element'; import '../../../backoffice/components/backoffice-notification-container.element'; +import '../../context/context-provider.element'; import './layouts/default'; +import { Meta, Story } from '@storybook/web-components'; +import { html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +import { UmbNotificationColor, UmbNotificationOptions, UmbNotificationService } from '.'; +import { UmbContextConsumerMixin } from '../../context'; + +import type { UmbNotificationDefaultData } from './layouts/default'; export default { title: 'API/Notifications/Overview', component: 'ucp-notification-layout-default', @@ -21,7 +22,7 @@ export default { } as Meta; @customElement('story-notification-default-example') -class StoryNotificationDefaultExampleElement extends UmbContextConsumerMixin(LitElement) { +export class StoryNotificationDefaultExampleElement extends UmbContextConsumerMixin(LitElement) { private _notificationService?: UmbNotificationService; connectedCallback(): void { diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer-consent.element.ts b/src/Umbraco.Web.UI.Client/src/installer/installer-consent.element.ts index be11bd5f45..edd051de63 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer-consent.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer-consent.element.ts @@ -4,7 +4,7 @@ import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { Subscription } from 'rxjs'; import { UmbContextConsumerMixin } from '../core/context'; -import { TelemetryModel } from '../core/models'; +import type { TelemetryModel } from '../core/models'; import { UmbInstallerContext } from './installer-context'; @customElement('umb-installer-consent') 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 2d8fb4db4e..27691f8ea4 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer-context.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer-context.ts @@ -1,7 +1,7 @@ import { BehaviorSubject, ReplaySubject } from 'rxjs'; import { getInstallSettings, postInstallSetup } from '../core/api/fetcher'; -import { PostInstallRequest, UmbracoInstaller } from '../core/models'; +import type { PostInstallRequest, UmbracoInstaller } from '../core/models'; export class UmbInstallerContext { private _data = new BehaviorSubject({ diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer-database.element.ts b/src/Umbraco.Web.UI.Client/src/installer/installer-database.element.ts index 4e3614bcbe..f52e9f1c12 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer-database.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer-database.element.ts @@ -5,7 +5,7 @@ import { Subscription } from 'rxjs'; import { postInstallSetup, postInstallValidateDatabase } from '../core/api/fetcher'; import { UmbContextConsumerMixin } from '../core/context'; -import { UmbracoInstallerDatabaseModel, UmbracoPerformInstallDatabaseConfiguration } from '../core/models'; +import type { UmbracoInstallerDatabaseModel, UmbracoPerformInstallDatabaseConfiguration } from '../core/models'; import { UmbInstallerContext } from './installer-context'; @customElement('umb-installer-database') diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer-error.element.ts b/src/Umbraco.Web.UI.Client/src/installer/installer-error.element.ts index 053d15f9ed..bdd87bba0e 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer-error.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer-error.element.ts @@ -1,7 +1,7 @@ import { css, CSSResultGroup, html, LitElement, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { ProblemDetails } from '../core/models'; +import type { ProblemDetails } from '../core/models'; @customElement('umb-installer-error') export class UmbInstallerError extends LitElement { 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 2935e73208..a9ee78c830 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts @@ -10,7 +10,7 @@ import { customElement, state } from 'lit/decorators.js'; import { postInstallSetup } from '../core/api/fetcher'; import { UmbContextProviderMixin } from '../core/context'; -import { ProblemDetails } from '../core/models'; +import type { ProblemDetails } from '../core/models'; import { UmbInstallerContext } from './installer-context'; @customElement('umb-installer') diff --git a/src/Umbraco.Web.UI.Client/src/mocks/App_Plugins/custom-entrypoint.js b/src/Umbraco.Web.UI.Client/src/mocks/App_Plugins/custom-entrypoint.js new file mode 100644 index 0000000000..8de2547b92 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/mocks/App_Plugins/custom-entrypoint.js @@ -0,0 +1,4 @@ +console.log('Hello from the custom entrypoint file!'); +export default function () { + console.log('Hello from the custom entrypoint inside the default function!'); +} diff --git a/src/Umbraco.Web.UI.Client/public/App_Plugins/property-editor.js b/src/Umbraco.Web.UI.Client/src/mocks/App_Plugins/property-editor.js similarity index 100% rename from src/Umbraco.Web.UI.Client/public/App_Plugins/property-editor.js rename to src/Umbraco.Web.UI.Client/src/mocks/App_Plugins/property-editor.js diff --git a/src/Umbraco.Web.UI.Client/public/App_Plugins/section.js b/src/Umbraco.Web.UI.Client/src/mocks/App_Plugins/section.js similarity index 100% rename from src/Umbraco.Web.UI.Client/public/App_Plugins/section.js rename to src/Umbraco.Web.UI.Client/src/mocks/App_Plugins/section.js diff --git a/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts index 3df67a6277..5154cd1d05 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts @@ -2,7 +2,7 @@ import { handlers as contentHandlers } from './domains/content.handlers'; import { handlers as dataTypeHandlers } from './domains/data-type.handlers'; import { handlers as documentTypeHandlers } from './domains/document-type.handlers'; import { handlers as installHandlers } from './domains/install.handlers'; -import { handlers as manifestsHandlers } from './domains/manifests.handlers'; +import * as manifestsHandlers from './domains/manifests.handlers'; import * as serverHandlers from './domains/server.handlers'; import { handlers as upgradeHandlers } from './domains/upgrade.handlers'; import { handlers as userHandlers } from './domains/user.handlers'; @@ -12,7 +12,6 @@ const handlers = [ ...contentHandlers, ...installHandlers, ...upgradeHandlers, - ...manifestsHandlers, ...userHandlers, ...dataTypeHandlers, ...documentTypeHandlers, @@ -29,4 +28,13 @@ switch (import.meta.env.VITE_UMBRACO_INSTALL_STATUS) { handlers.push(serverHandlers.serverRunningHandler); } +switch (import.meta.env.MODE) { + case 'development': + handlers.push(manifestsHandlers.manifestDevelopmentHandler); + break; + + default: + handlers.push(manifestsHandlers.manifestEmptyHandler); +} + export { handlers }; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/install.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/install.handlers.ts index 969a588b54..d72ef78b14 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/install.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/install.handlers.ts @@ -1,7 +1,7 @@ import { rest } from 'msw'; import umbracoPath from '../../core/helpers/umbraco-path'; -import { +import type { PostInstallRequest, ProblemDetails, UmbracoInstaller, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/manifests.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/manifests.handlers.ts index 07c1cafb1b..d2288693c2 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/manifests.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/manifests.handlers.ts @@ -1,38 +1,53 @@ import { rest } from 'msw'; -// TODO: set up schema -export const handlers = [ - rest.get('/umbraco/backoffice/manifests', (_req, res, ctx) => { - console.warn('Please move to schema'); - return res( - // Respond with a 200 status code - ctx.status(200), - ctx.json({ - manifests: [ - { - type: 'section', - alias: 'My.Section.Custom', - name: 'Custom Section', - js: '/App_Plugins/section.js', - elementName: 'my-section-custom', - meta: { - pathname: 'my-custom', - weight: 1, - }, +import umbracoPath from '../../core/helpers/umbraco-path'; + +import type { ManifestsResponse } from '../../core/models'; + +export const manifestDevelopmentHandler = rest.get(umbracoPath('/manifests'), (_req, res, ctx) => { + return res( + // Respond with a 200 status code + ctx.status(200), + ctx.json({ + manifests: [ + { + type: 'section', + alias: 'My.Section.Custom', + name: 'Custom Section', + js: '/src/mocks/App_Plugins/section.js', + elementName: 'my-section-custom', + meta: { + pathname: 'my-custom', + weight: 1, }, - { - type: 'propertyEditorUI', - alias: 'My.PropertyEditorUI.Custom', - name: 'My Custom Property Editor UI', - js: '/App_Plugins/property-editor.js', - elementName: 'my-property-editor-ui-custom', - meta: { - icon: 'document', - group: 'common', - }, + }, + { + type: 'propertyEditorUI', + alias: 'My.PropertyEditorUI.Custom', + name: 'My Custom Property Editor UI', + js: '/src/mocks/App_Plugins/property-editor.js', + elementName: 'my-property-editor-ui-custom', + meta: { + icon: 'document', + group: 'common', }, - ], - }) - ); - }), -]; + }, + { + type: 'entrypoint', + alias: 'My.Entrypoint.Custom', + js: '/src/mocks/App_Plugins/custom-entrypoint.js', + }, + ], + }) + ); +}); + +export const manifestEmptyHandler = rest.get(umbracoPath('/manifests'), (_req, res, ctx) => { + return res( + // Respond with a 200 status code + ctx.status(200), + ctx.json({ + manifests: [], + }) + ); +}); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/server.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/server.handlers.ts index 5666b48de8..417a8b8706 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/server.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/server.handlers.ts @@ -1,7 +1,7 @@ import { rest } from 'msw'; import umbracoPath from '../../core/helpers/umbraco-path'; -import { StatusResponse, VersionResponse } from '../../core/models'; +import type { StatusResponse, VersionResponse } from '../../core/models'; export const serverRunningHandler = rest.get(umbracoPath('/server/status'), (_req, res, ctx) => { return res( diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/upgrade.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/upgrade.handlers.ts index 3006a21baa..6b00f51461 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/upgrade.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/upgrade.handlers.ts @@ -1,7 +1,7 @@ import { rest } from 'msw'; import umbracoPath from '../../core/helpers/umbraco-path'; -import { PostInstallRequest, UmbracoUpgrader } from '../../core/models'; +import type { PostInstallRequest, UmbracoUpgrader } from '../../core/models'; export const handlers = [ rest.get(umbracoPath('/upgrade/settings'), (_req, res, ctx) => { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/user.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/user.handlers.ts index 111c38aa7c..4d31e91e55 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/user.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/user.handlers.ts @@ -1,7 +1,7 @@ import { rest } from 'msw'; import umbracoPath from '../../core/helpers/umbraco-path'; -import { AllowedSectionsResponse, UserResponse } from '../../core/models'; +import type { AllowedSectionsResponse, UserResponse } from '../../core/models'; let isAuthenticated = false; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/e2e-handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/e2e-handlers.ts index 7e20f40369..f573be6393 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/e2e-handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/e2e-handlers.ts @@ -1,19 +1,19 @@ import { handlers as contentHandlers } from './domains/content.handlers'; +import { handlers as dataTypeHandlers } from './domains/data-type.handlers'; +import { handlers as documentTypeHandlers } from './domains/document-type.handlers'; import { handlers as installHandlers } from './domains/install.handlers'; -import { handlers as manifestsHandlers } from './domains/manifests.handlers'; +import * as manifestsHandlers from './domains/manifests.handlers'; import * as serverHandlers from './domains/server.handlers'; import { handlers as upgradeHandlers } from './domains/upgrade.handlers'; import { handlers as userHandlers } from './domains/user.handlers'; -import { handlers as dataTypeHandlers } from './domains/data-type.handlers'; -import { handlers as documentTypeHandlers } from './domains/document-type.handlers'; export const handlers = [ serverHandlers.serverRunningHandler, serverHandlers.serverVersionHandler, + manifestsHandlers.manifestDevelopmentHandler, ...contentHandlers, ...installHandlers, ...upgradeHandlers, - ...manifestsHandlers, ...userHandlers, ...dataTypeHandlers, ...documentTypeHandlers, diff --git a/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts b/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts index d86cfbb8af..48fa4ad186 100644 --- a/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts @@ -1,14 +1,14 @@ -import { UmbExtensionManifestCore } from './core/extension'; +import type { ManifestTypes } from './core/models'; // TODO: consider moving weight from meta to the main part of the manifest. We need it for every extension. // TODO: consider adding a label property as part of the meta. It might make sense to have an "extension" name label where one is needed. -export const internalManifests: Array = [ +export const internalManifests: Array Promise }> = [ { type: 'section', alias: 'Umb.Section.Content', name: 'Content', elementName: 'umb-content-section', - js: () => import('./backoffice/sections/content/content-section.element'), + loader: () => import('./backoffice/sections/content/content-section.element'), meta: { pathname: 'content', // TODO: how to we want to support pretty urls? weight: 50, @@ -19,28 +19,28 @@ export const internalManifests: Array = [ alias: 'Umb.Section.Media', name: 'Media', elementName: 'umb-media-section', - js: () => import('./backoffice/sections/media/media-section.element'), + loader: () => import('./backoffice/sections/media/media-section.element'), meta: { pathname: 'media', // TODO: how to we want to support pretty urls? weight: 50, }, }, - { - type: 'section', - alias: 'Umb.Section.Members', - name: 'Members', - elementName: 'umb-members-section', - meta: { - pathname: 'members', - weight: 30, - }, - }, + // { + // type: 'section', + // alias: 'Umb.Section.Members', + // name: 'Members', + // elementName: 'umb-members-section', + // meta: { + // pathname: 'members', + // weight: 30, + // }, + // }, { type: 'section', alias: 'Umb.Section.Settings', name: 'Settings', elementName: 'umb-settings-section', - js: () => import('./backoffice/sections/settings/settings-section.element'), + loader: () => import('./backoffice/sections/settings/settings-section.element'), meta: { pathname: 'settings', // TODO: how to we want to support pretty urls? weight: 20, @@ -51,7 +51,7 @@ export const internalManifests: Array = [ alias: 'Umb.Dashboard.Welcome', name: 'Welcome', elementName: 'umb-dashboard-welcome', - js: () => import('./backoffice/dashboards/welcome/dashboard-welcome.element'), + loader: () => import('./backoffice/dashboards/welcome/dashboard-welcome.element'), meta: { sections: ['Umb.Section.Content'], pathname: 'welcome', // TODO: how to we want to support pretty urls? @@ -63,7 +63,7 @@ export const internalManifests: Array = [ alias: 'Umb.Dashboard.RedirectManagement', name: 'Redirect Management', elementName: 'umb-dashboard-redirect-management', - js: () => import('./backoffice/dashboards/redirect-management/dashboard-redirect-management.element'), + loader: () => import('./backoffice/dashboards/redirect-management/dashboard-redirect-management.element'), meta: { sections: ['Umb.Section.Content'], pathname: 'redirect-management', // TODO: how to we want to support pretty urls? @@ -75,7 +75,7 @@ export const internalManifests: Array = [ alias: 'Umb.Dashboard.SettingsAbout', name: 'Settings About', elementName: 'umb-dashboard-settings-about', - js: () => import('./backoffice/dashboards/settings-about/dashboard-settings-about.element'), + loader: () => import('./backoffice/dashboards/settings-about/dashboard-settings-about.element'), meta: { label: 'About', sections: ['Umb.Section.Settings'], @@ -88,7 +88,7 @@ export const internalManifests: Array = [ alias: 'Umb.Dashboard.ExamineManagement', name: 'Examine Management', elementName: 'umb-dashboard-examine-management', - js: () => import('./backoffice/dashboards/examine-management/dashboard-examine-management.element'), + loader: () => import('./backoffice/dashboards/examine-management/dashboard-examine-management.element'), meta: { sections: ['Umb.Section.Settings'], pathname: 'examine-management', // TODO: how to we want to support pretty urls? @@ -100,7 +100,7 @@ export const internalManifests: Array = [ alias: 'Umb.Dashboard.ModelsBuilder', name: 'Models Builder', elementName: 'umb-dashboard-models-builder', - js: () => import('./backoffice/dashboards/models-builder/dashboard-models-builder.element'), + loader: () => import('./backoffice/dashboards/models-builder/dashboard-models-builder.element'), meta: { sections: ['Umb.Section.Settings'], pathname: 'models-builder', // TODO: how to we want to support pretty urls? @@ -112,7 +112,7 @@ export const internalManifests: Array = [ alias: 'Umb.Dashboard.MediaManagement', name: 'Media', elementName: 'umb-dashboard-media-management', - js: () => import('./backoffice/dashboards/media-management/dashboard-media-management.element'), + loader: () => import('./backoffice/dashboards/media-management/dashboard-media-management.element'), meta: { sections: ['Umb.Section.Media'], pathname: 'media-management', // TODO: how to we want to support pretty urls? @@ -123,7 +123,7 @@ export const internalManifests: Array = [ type: 'propertyEditorUI', alias: 'Umb.PropertyEditorUI.Text', name: 'Text', - js: () => import('./backoffice/property-editors/property-editor-text.element'), + loader: () => import('./backoffice/property-editors/property-editor-text.element'), meta: { icon: 'edit', group: 'common', @@ -134,7 +134,7 @@ export const internalManifests: Array = [ alias: 'Umb.PropertyEditorUI.Textarea', name: 'Textarea', elementName: 'umb-property-editor-textarea', - js: () => import('./backoffice/property-editors/property-editor-textarea.element'), + loader: () => import('./backoffice/property-editors/property-editor-textarea.element'), meta: { icon: 'edit', group: 'common', @@ -144,7 +144,7 @@ export const internalManifests: Array = [ type: 'propertyEditorUI', alias: 'Umb.PropertyEditorUI.ContextExample', name: 'Context Example', - js: () => import('./backoffice/property-editors/property-editor-context-example.element'), + loader: () => import('./backoffice/property-editors/property-editor-context-example.element'), meta: { icon: 'favorite', group: 'common', @@ -155,7 +155,7 @@ export const internalManifests: Array = [ alias: 'Umb.EditorView.ContentEdit', name: 'Content', elementName: 'umb-editor-view-node-edit', - js: () => import('./backoffice/editors/shared/node/views/edit/editor-view-node-edit.element'), + loader: () => import('./backoffice/editors/shared/node/views/edit/editor-view-node-edit.element'), meta: { // TODO: how do we want to filter where editor views are shown? https://our.umbraco.com/documentation/extending/Content-Apps/#setting-up-the-plugin // this is a temp solution @@ -170,7 +170,7 @@ export const internalManifests: Array = [ alias: 'Umb.EditorView.ContentInfo', name: 'Info', elementName: 'umb-editor-view-node-info', - js: () => import('./backoffice/editors/shared/node/views/info/editor-view-node-info.element'), + loader: () => import('./backoffice/editors/shared/node/views/info/editor-view-node-info.element'), meta: { // TODO: how do we want to filter where editor views are shown? https://our.umbraco.com/documentation/extending/Content-Apps/#setting-up-the-plugin // this is a temp solution @@ -185,7 +185,7 @@ export const internalManifests: Array = [ alias: 'Umb.EditorView.DataTypeEdit', name: 'Edit', elementName: 'umb-editor-view-data-type-edit', - js: () => import('./backoffice/editors/data-type/views/editor-view-data-type-edit.element'), + loader: () => import('./backoffice/editors/data-type/views/editor-view-data-type-edit.element'), meta: { // TODO: how do we want to filter where editor views are shown? https://our.umbraco.com/documentation/extending/Content-Apps/#setting-up-the-plugin // this is a temp solution @@ -200,7 +200,7 @@ export const internalManifests: Array = [ alias: 'Umb.EditorView.DocumentTypeDesign', name: 'Design', elementName: 'umb-editor-view-document-type-design', - js: () => import('./backoffice/editors/document-type/views/editor-view-document-type-design.element'), + loader: () => import('./backoffice/editors/document-type/views/editor-view-document-type-design.element'), meta: { // TODO: how do we want to filter where editor views are shown? https://our.umbraco.com/documentation/extending/Content-Apps/#setting-up-the-plugin // this is a temp solution @@ -215,7 +215,7 @@ export const internalManifests: Array = [ alias: 'Umb.PropertyAction.Copy', name: 'Copy', elementName: 'umb-property-action-copy', - js: () => import('./backoffice/property-actions/property-action-copy.element'), + loader: () => import('./backoffice/property-actions/property-action-copy.element'), meta: { propertyEditors: ['Umb.PropertyEditorUI.Text'], }, @@ -225,7 +225,7 @@ export const internalManifests: Array = [ alias: 'Umb.PropertyAction.Clear', name: 'Clear', elementName: 'umb-property-action-clear', - js: () => import('./backoffice/property-actions/property-action-clear.element'), + loader: () => import('./backoffice/property-actions/property-action-clear.element'), meta: { propertyEditors: ['Umb.PropertyEditorUI.Text'], }, @@ -235,7 +235,7 @@ export const internalManifests: Array = [ alias: 'Umb.PropertyEditorUI.ContentPicker', name: 'ContentPicker', elementName: 'umb-property-editor-content-picker', - js: () => import('./backoffice/property-editors/property-editor-content-picker.element'), + loader: () => import('./backoffice/property-editors/property-editor-content-picker.element'), meta: { icon: 'document', group: 'common', diff --git a/src/Umbraco.Web.UI.Client/src/upgrader/upgrader-view.element.ts b/src/Umbraco.Web.UI.Client/src/upgrader/upgrader-view.element.ts index 8b694109de..d51d7d5bd3 100644 --- a/src/Umbraco.Web.UI.Client/src/upgrader/upgrader-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/upgrader/upgrader-view.element.ts @@ -2,7 +2,7 @@ import { css, CSSResultGroup, html, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; -import { UmbracoUpgrader } from '../core/models'; +import type { UmbracoUpgrader } from '../core/models'; /** * @element umb-upgrader-view diff --git a/src/Umbraco.Web.UI.Client/temp-schema-generator/api.ts b/src/Umbraco.Web.UI.Client/temp-schema-generator/api.ts index 51cdd0acc6..c7eb465583 100644 --- a/src/Umbraco.Web.UI.Client/temp-schema-generator/api.ts +++ b/src/Umbraco.Web.UI.Client/temp-schema-generator/api.ts @@ -1,4 +1,5 @@ import './installer'; +import './manifests'; import './server'; import './upgrader'; import './user'; diff --git a/src/Umbraco.Web.UI.Client/temp-schema-generator/manifests.ts b/src/Umbraco.Web.UI.Client/temp-schema-generator/manifests.ts new file mode 100644 index 0000000000..47bf3b281b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/temp-schema-generator/manifests.ts @@ -0,0 +1,109 @@ +import { body, defaultResponse, endpoint, response } from '@airtasker/spot'; + +import { ProblemDetails } from './models'; + +@endpoint({ method: 'GET', path: '/manifests' }) +export class Manifests { + @response({ status: 200 }) + response(@body body: ManifestsResponse) {} + + @defaultResponse + default(@body body: ProblemDetails) {} +} + +export type Manifest = + | IManifestSection + | IManifestPropertyEditorUI + | IManifestDashboard + | IManifestEditorView + | IManifestPropertyAction + | IManifestEntrypoint + | IManifestCustom; + +export type ManifestStandardTypes = + | 'section' + | 'propertyEditorUI' + | 'dashboard' + | 'editorView' + | 'propertyAction' + | 'entrypoint'; + +export interface ManifestsResponse { + manifests: Manifest[]; +} + +export interface IManifest { + type: string; + alias: string; +} + +export interface MetaSection { + pathname: string; + weight: number; +} + +export interface MetaPropertyEditorUI { + icon: string; + group: string; +} + +export interface MetaDashboard { + sections: string[]; + pathname: string; + weight: number; + label?: string; +} + +export interface MetaEditorView { + editors: string[]; + pathname: string; + weight: number; + icon: string; +} + +export interface MetaPropertyAction { + propertyEditors: string[]; +} + +export interface IManifestCustom extends IManifest { + type: 'custom'; + meta?: {}; +} + +export interface IManifestElement extends IManifest { + type: ManifestStandardTypes; + name: string; + js?: string; + elementName?: string; + meta?: {}; +} + +export interface IManifestSection extends IManifestElement { + type: 'section'; + meta: MetaSection; +} + +export interface IManifestPropertyEditorUI extends IManifestElement { + type: 'propertyEditorUI'; + meta: MetaPropertyEditorUI; +} + +export interface IManifestDashboard extends IManifestElement { + type: 'dashboard'; + meta: MetaDashboard; +} + +export interface IManifestEditorView extends IManifestElement { + type: 'editorView'; + meta: MetaEditorView; +} + +export interface IManifestPropertyAction extends IManifestElement { + type: 'propertyAction'; + meta: MetaPropertyAction; +} + +export interface IManifestEntrypoint extends IManifest { + type: 'entrypoint'; + js: string; +}