Merge remote-tracking branch 'origin/main' into feature/document-type-workspace-take-5

# Conflicts:
#	libs/modal/modal-handler.ts
#	package-lock.json
#	src/backoffice/core/components/input-multi-url-picker/input-multi-url-picker.element.ts
#	src/backoffice/core/components/input-multi-url/input-multi-url.element.ts
#	src/packages/core/components/input-multi-url-picker/input-multi-url-picker.element.ts
#	src/packages/core/components/input-multi-url-picker/input-multi-url-picker.stories.ts
#	src/packages/core/modals/property-settings/property-settings-modal.element.ts
#	src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-design.element.ts
#	src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-design.stories.ts
#	src/shared/router/encode-folder-name.function.ts
This commit is contained in:
Niels Lyngsø
2023-05-17 09:34:51 +02:00
1902 changed files with 22273 additions and 5120 deletions

View File

@@ -44,7 +44,7 @@ The frontend has an API formatter that takes the OpenAPI schema file and convert
**How to convert it:**
* Run `npm run generate:api`
- Run `npm run generate:api`
## A contribution example
@@ -56,9 +56,9 @@ The frontend has an API formatter that takes the OpenAPI schema file and convert
Links for Lit examples and documentation:
* [https://lit.dev](https://lit.dev)
* [https://lit.dev/docs/](https://lit.dev/docs/)
* [https://lit.dev/playground/](https://lit.dev/playground/)
- [https://lit.dev](https://lit.dev)
- [https://lit.dev/docs/](https://lit.dev/docs/)
- [https://lit.dev/playground/](https://lit.dev/playground/)
### Functionality
@@ -128,28 +128,29 @@ To declare the Published Cache Status Dashboard as a new manifest, we need to ad
Lets go through each of these properties…
* Type: can be one of the following:
* section - examples include: `Content`, `Media`
* dashboard - a view within a section. Examples include: the welcome dashboard
* propertyEditorUI
* editorView
* propertyAction
* tree
* editor
* treeItemAction
- Type: can be one of the following:
* Alias: is the unique key used to identify this item.
* Name: is the human-readable name for this item.
- section - examples include: `Content`, `Media`
- dashboard - a view within a section. Examples include: the welcome dashboard
- propertyEditorUI
- editorView
- propertyAction
- tree
- editor
- treeItemAction
* ElementName: this is the customElementName declared on the element at the top of the file i.e
- Alias: is the unique key used to identify this item.
- Name: is the human-readable name for this item.
- ElementName: this is the customElementName declared on the element at the top of the file i.e
```typescript
@customElement('umb-dashboard-published-status')
```
* Loader: references a function call to import the file that the element is declared within
- Loader: references a function call to import the file that the element is declared within
* Meta: allows us to reference additional data - in our case we can specify the section that our dashboard will sit within, the pathname that will be displayed in the url and the weight of the section
- Meta: allows us to reference additional data - in our case we can specify the section that our dashboard will sit within, the pathname that will be displayed in the url and the weight of the section
## API mock handlers
@@ -162,19 +163,19 @@ From the existing functionality, we can see that this is a string message that i
So to define this, we must first add a handler for the Published Status called `published-status.handlers.ts` within the mocks/domains folder. In this file we will have code that looks like the following:
```typescript
import { rest } from 'msw';
const { rest } = window.MockServiceWorker;
import { umbracoPath } from '@umbraco-cms/utils';
export const handlers = [
rest.get(umbracoPath('/published-cache/status'), (_req, res, ctx) => {
return res(
// Respond with a 200 status code
ctx.status(200),
ctx.json<string>(
'Database cache is ok. ContentStore contains 1 item and has 1 generation and 0 snapshot. MediaStore contains 5 items and has 1 generation and 0 snapshot.'
)
);
}),
rest.get(umbracoPath('/published-cache/status'), (_req, res, ctx) => {
return res(
// Respond with a 200 status code
ctx.status(200),
ctx.json<string>(
'Database cache is ok. ContentStore contains 1 item and has 1 generation and 0 snapshot. MediaStore contains 5 items and has 1 generation and 0 snapshot.'
)
);
}),
];
```
@@ -211,7 +212,7 @@ In depth: [https://storybook.js.org/docs/web-components/get-started/introduction
Reference: [https://ambitious-stone-0033b3603.1.azurestaticapps.net/](https://ambitious-stone-0033b3603.1.azurestaticapps.net/)
* Locally: `npm run storybook`
- Locally: `npm run storybook`
For Umbraco UI stories, please navigate to [https://uui.umbraco.com/](https://uui.umbraco.com/)

View File

@@ -4,17 +4,23 @@ on:
push:
branches:
- main
# pull_request:
# types: [opened, synchronize, reopened, closed]
# branches:
# - main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
workflow_dispatch:
inputs:
issue_number:
type: number
description: 'Issue/PR Number to comment on'
required: false
env:
NODE_OPTIONS: --max_old_space_size=16384
jobs:
build_and_deploy_job:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && github.event.action != 'closed' && contains(github.event.pull_request.labels.*.name, 'storybook'))
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
@@ -27,17 +33,39 @@ jobs:
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_AMBITIOUS_STONE_0033B3603 }}
repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
action: "upload"
action: 'upload'
###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
app_location: "/" # App source code path
app_build_command: "npm run build-storybook"
api_location: "" # Api source code path - optional
output_location: "/storybook-static" # Built app content directory - optional
app_location: '/' # App source code path
app_build_command: 'npm run build-storybook'
api_location: '' # Api source code path - optional
output_location: '/storybook-static' # Built app content directory - optional
###### End of Repository/Build Configurations ######
- name: Comment on PR
# azure/static-web-apps-deploy doesn't support workflow_dispatch, so we need to manually comment on the PR
if: github.event_name == 'workflow_dispatch' && inputs.issue_number != null
uses: actions/github-script@v6
env:
ISSUE_NUMBER: ${{ inputs.issue_number }}
SITE_URL: ${{ steps.builddeploy.outputs.static_web_app_url }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.addLabels({
issue_number: process.env.ISSUE_NUMBER,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['storybook']
})
github.rest.issues.createComment({
issue_number: process.env.ISSUE_NUMBER,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Storybook is available at: ${process.env.SITE_URL}`
})
close_pull_request_job:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
if: github.event_name == 'pull_request' && github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'storybook')
runs-on: ubuntu-latest
name: Close Pull Request Job
steps:
@@ -45,5 +73,6 @@ jobs:
id: closepullrequest
uses: Azure/static-web-apps-deploy@v1
with:
app_location: '/'
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_AMBITIOUS_STONE_0033B3603 }}
action: "close"
action: 'close'

View File

@@ -27,12 +27,12 @@ jobs:
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ASHY_BAY_09F36A803 }}
repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
action: "upload"
action: 'upload'
###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
app_location: "/" # App source code path
api_location: "api" # Api source code path - optional
output_location: "dist" # Built app content directory - optional
app_location: '/' # App source code path
api_location: 'api' # Api source code path - optional
output_location: 'dist' # Built app content directory - optional
###### End of Repository/Build Configurations ######
close_pull_request_job:
@@ -44,5 +44,6 @@ jobs:
id: closepullrequest
uses: Azure/static-web-apps-deploy@v1
with:
app_location: '/'
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ASHY_BAY_09F36A803 }}
action: "close"
action: 'close'

View File

@@ -1 +1 @@
18.15
18.16

View File

@@ -1,8 +1,23 @@
import { StorybookConfig } from '@storybook/web-components-vite';
import remarkGfm from 'remark-gfm';
const config: StorybookConfig = {
stories: ['../@(src|libs|apps|storybook)/**/*.mdx', '../@(src|libs|apps|storybook)/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-a11y'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-a11y',
{
name: '@storybook/addon-docs',
options: {
mdxPluginOptions: {
mdxCompileOptions: {
remarkPlugins: [remarkGfm],
},
},
},
},
],
framework: {
name: '@storybook/web-components-vite',
options: {},

View File

@@ -23,7 +23,7 @@
line-height: 1.3em;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/msw/lib/iife/index.js"></script>
<script>
(function () {
window.addEventListener('load', () => {
@@ -40,3 +40,4 @@
});
})();
</script>

View File

@@ -1,5 +1,5 @@
import '@umbraco-ui/uui-css/dist/uui-css.css';
import '../src/core/css/custom-properties.css';
import '../src/shared/css/custom-properties.css';
import 'element-internals-polyfill';
import '@umbraco-ui/uui';
@@ -8,32 +8,32 @@ import { html } from 'lit';
import { initialize, mswDecorator } from 'msw-storybook-addon';
import { setCustomElements } from '@storybook/web-components';
import { UmbDataTypeStore } from '../src/backoffice/settings/data-types/repository/data-type.store.ts';
import { UmbDocumentTypeStore } from '../src/backoffice/documents/document-types/repository/document-type.store.ts';
import { UmbDocumentStore } from '../src/backoffice/documents/documents/repository/document.store.ts';
import { UmbDocumentTreeStore } from '../src/backoffice/documents/documents/repository/document.tree.store.ts';
import { UmbDataTypeStore } from '../src/packages/settings/data-types/repository/data-type.store.ts';
import { UmbDocumentTypeStore } from '../src/packages/documents/document-types/repository/document-type.store.ts';
import { UmbDocumentStore } from '../src/packages/documents/documents/repository/document.store.ts';
import { UmbDocumentTreeStore } from '../src/packages/documents/documents/repository/document.tree.store.ts';
import customElementManifests from '../dist/libs/custom-elements.json';
import { UmbIconStore } from '../src/core/stores/icon/icon.store';
import { onUnhandledRequest } from '../src/core/mocks/browser';
import { handlers } from '../src/core/mocks/browser-handlers';
import { UmbIconRegistry } from '../src/shared/icon-registry/icon.registry';
import { onUnhandledRequest } from '../src/shared/mocks';
import { handlers } from '../src/shared/mocks/browser-handlers';
import { UMB_MODAL_CONTEXT_TOKEN, UmbModalContext } from '../libs/modal';
import { UmbLitElement } from '../src/core/lit-element';
import { UmbLitElement } from '../src/shared/lit-element';
import { umbExtensionsRegistry } from '../libs/extensions-api';
import { umbExtensionsRegistry } from '../libs/extension-registry';
import '../src/core/context-provider/context-provider.element';
import '../src/core/controller-host/controller-host-test.element';
import '../src/backoffice/core/components';
import '../libs/context-api/provide/context-provider.element';
import '../libs/controller-api/controller-host-initializer.element.ts';
import '../src/packages/core/components';
import { manifests as documentManifests } from '../src/backoffice/documents';
import { manifests as documentManifests } from '../src/packages/documents';
class UmbStoryBookElement extends UmbLitElement {
_umbIconStore = new UmbIconStore();
_umbIconRegistry = new UmbIconRegistry();
constructor() {
super();
this._umbIconStore.attach(this);
this._umbIconRegistry.attach(this);
this._registerExtensions(documentManifests);
this.provideContext(UMB_MODAL_CONTEXT_TOKEN, new UmbModalContext(this));
}
@@ -56,19 +56,27 @@ customElements.define('umb-storybook', UmbStoryBookElement);
const storybookProvider = (story) => html` <umb-storybook>${story()}</umb-storybook> `;
const dataTypeStoreProvider = (story) => html`
<umb-controller-host-test .create=${(host) => new UmbDataTypeStore(host)}>${story()}</umb-controller-host-test>
<umb-controller-host-initializer .create=${(host) => new UmbDataTypeStore(host)}
>${story()}</umb-controller-host-initializer
>
`;
const documentTypeStoreProvider = (story) => html`
<umb-controller-host-test .create=${(host) => new UmbDocumentTypeStore(host)}>${story()}</umb-controller-host-test>
<umb-controller-host-initializer .create=${(host) => new UmbDocumentTypeStore(host)}
>${story()}</umb-controller-host-initializer
>
`;
const documentStoreProvider = (story) => html`
<umb-controller-host-test .create=${(host) => new UmbDocumentStore(host)}>${story()}</umb-controller-host-test>
<umb-controller-host-initializer .create=${(host) => new UmbDocumentStore(host)}
>${story()}</umb-controller-host-initializer
>
`;
const documentTreeStoreProvider = (story) => html`
<umb-controller-host-test .create=${(host) => new UmbDocumentTreeStore(host)}>${story()}</umb-controller-host-test>
<umb-controller-host-initializer .create=${(host) => new UmbDocumentTreeStore(host)}
>${story()}</umb-controller-host-initializer
>
`;
// Initialize MSW

View File

@@ -1,5 +1,5 @@
// TODO: could these be renamed as login providers?
import type { ManifestExternalLoginProvider } from '@umbraco-cms/backoffice/extensions-registry';
import type { ManifestExternalLoginProvider } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestExternalLoginProvider> = [
{

View File

@@ -1,5 +1,4 @@
import { ManifestTypes } from '@umbraco-cms/backoffice/extensions-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api';
import { ManifestTypes, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { manifests as externalLoginProviders } from './external-login-providers/manifests';
import '@umbraco-ui/uui-css/dist/uui-css.css';

View File

@@ -6,7 +6,7 @@ const path = pathModule.default;
const getDirName = path.dirname;
const glob = globModule.default;
const moduleDirectory = 'libs/store/icon/';
const moduleDirectory = 'src/shared/icon-registry/';
const iconsSVGDirectory = `${moduleDirectory}svgs/`;
const iconsOutputDirectory = `public-assets/icons/`;

View File

@@ -1,4 +1,4 @@
import { rest } from 'msw';
const { rest } = window.MockServiceWorker;
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
import { ProblemDetailsModel, RuntimeLevelModel, ServerStatusResponseModel } from '@umbraco-cms/backoffice/backend-api';

View File

@@ -2,7 +2,7 @@ import { expect, test as base } from '@playwright/test';
import { createWorkerFixture } from 'playwright-msw';
import type { MockServiceWorker } from 'playwright-msw';
import { handlers } from '../src/core/mocks/e2e-handlers';
import { handlers } from '../src/shared/mocks/e2e-handlers';
const test = base.extend<{
worker: MockServiceWorker;

View File

@@ -1,4 +1,4 @@
import { rest } from 'msw';
const { rest } = window.MockServiceWorker;
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
import { ProblemDetailsModel, RuntimeLevelModel, ServerStatusResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { expect, test } from './test';

View File

@@ -5,11 +5,10 @@
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Umbraco</title>
<script type="module" src="/src/index.ts"></script>
<script src="/node_modules/msw/lib/iife/index.js"></script>
<script type="module" src="index.ts"></script>
<base href="/" />
</head>
<body class="uui-font uui-text" style="margin: 0; padding: 0; overflow: hidden">
<umb-app></umb-app>
</body>
<body class="uui-font uui-text" style="margin: 0; padding: 0; overflow: hidden"></body>
</html>

View File

@@ -0,0 +1,21 @@
import { UmbAppElement } from './src/apps/app/app.element';
import { startMockServiceWorker } from './src/shared/mocks';
if (import.meta.env.VITE_UMBRACO_USE_MSW === 'on') {
startMockServiceWorker();
}
const appElement = new UmbAppElement();
const isMocking = import.meta.env.VITE_UMBRACO_USE_MSW === 'on';
if (!isMocking) {
appElement.serverUrl = import.meta.env.VITE_UMBRACO_API_URL;
}
if (import.meta.env.DEV) {
appElement.backofficePath = '/';
}
appElement.bypassAuth = isMocking;
document.body.appendChild(appElement);

View File

@@ -40,7 +40,7 @@ Create an umbraco-package.json file in the root of your package.
Then create a dashboard.js file the same folder.
```javascript
import { UmbElementMixin } from '@umbraco-cms/backoffice/element';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import { UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification';
const template = document.createElement('template');
@@ -106,12 +106,11 @@ Then go to the element located in `src/my-element.ts` and replace it with the fo
// src/my-element.ts
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification';
@customElement('my-element')
export default class MyElement extends UmbElementMixin(LitElement) {
private _notificationContext?: UmbNotificationContext;
constructor() {

View File

@@ -1,4 +1,4 @@
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import {
UmbArrayState,
@@ -6,7 +6,8 @@ import {
UmbObjectState,
UmbObserverController,
} from '@umbraco-cms/backoffice/observable-api';
import { umbExtensionsRegistry, createExtensionClass } from '@umbraco-cms/backoffice/extensions-api';
import { createExtensionClass } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbCollectionRepository } from '@umbraco-cms/backoffice/repository';
import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection';

View File

@@ -1,6 +1,6 @@
import { PropertyContainerTypes, UmbContentTypePropertyStructureManager } from './content-type-structure-manager.class';
import { PropertyTypeContainerResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbArrayState, UmbBooleanState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
export class UmbContentTypeContainerStructureHelper {

View File

@@ -3,7 +3,7 @@ import {
DocumentTypePropertyTypeResponseModel,
PropertyTypeResponseModelBaseModel,
} from '@umbraco-cms/backoffice/backend-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbArrayState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
export class UmbContentTypePropertyStructureHelper {

View File

@@ -6,7 +6,7 @@ import {
PropertyTypeResponseModelBaseModel,
DocumentTypeResponseModel,
} from '@umbraco-cms/backoffice/backend-api';
import { UmbControllerHostElement, UmbControllerInterface } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement, UmbControllerInterface } from '@umbraco-cms/backoffice/controller-api';
import {
UmbArrayState,
UmbObserverController,

View File

@@ -1,7 +1,7 @@
import { UmbContextToken } from '../token/context-token';
import { UmbContextConsumer } from './context-consumer';
import { UmbContextCallback } from './context-request.event';
import type { UmbControllerHostElement, UmbControllerInterface } from '@umbraco-cms/backoffice/controller';
import type { UmbControllerHostElement, UmbControllerInterface } from '@umbraco-cms/backoffice/controller-api';
export class UmbContextConsumerController<T = unknown>
extends UmbContextConsumer<UmbControllerHostElement, T>

View File

@@ -4,5 +4,6 @@ export * from './consume/context-request.event';
export * from './provide/context-provider.controller';
export * from './provide/context-provider';
export * from './provide/context-provide.event';
export * from './provide/context-provider.element';
export * from './token/';
export * from './debug/context-data.function';
export * from './debug/context-data.function';

View File

@@ -1,6 +1,6 @@
import { UmbContextToken } from '../token/context-token';
import { UmbContextProvider } from './context-provider';
import type { UmbControllerHostElement, UmbControllerInterface } from '@umbraco-cms/backoffice/controller';
import type { UmbControllerHostElement, UmbControllerInterface } from '@umbraco-cms/backoffice/controller-api';
export class UmbContextProviderController<T = unknown>
extends UmbContextProvider<UmbControllerHostElement>

View File

@@ -1,14 +1,16 @@
import { expect, fixture, html } from '@open-wc/testing';
import { customElement } from 'lit/decorators.js';
import { UmbContextConsumerController } from '../consume/context-consumer.controller';
import { UmbContextProviderElement } from './context-provider.element';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { UmbControllerHostMixin } from '@umbraco-cms/backoffice/controller-api';
@customElement('umb-context-test')
export class UmbContextTestElement extends UmbLitElement {
@customElement('umb-test-context')
export class UmbTestContextElement extends UmbControllerHostMixin(HTMLElement) {
public value: string | null = null;
constructor() {
super();
this.consumeContext<string>('test-context', (value) => {
new UmbContextConsumerController<string>(this, 'test-context', (value) => {
this.value = value;
});
}
@@ -16,16 +18,16 @@ export class UmbContextTestElement extends UmbLitElement {
describe('UmbContextProvider', () => {
let element: UmbContextProviderElement;
let consumer: UmbContextTestElement;
let consumer: UmbTestContextElement;
const contextValue = 'test-value';
beforeEach(async () => {
element = await fixture(
html` <umb-context-provider key="test-context" .value=${contextValue}>
<umb-context-test></umb-context-test>
<umb-test-context></umb-test-context>
</umb-context-provider>`
);
consumer = element.getElementsByTagName('umb-context-test')[0] as UmbContextTestElement;
consumer = element.getElementsByTagName('umb-test-context')[0] as UmbTestContextElement;
});
it('is defined with its own instance', () => {

View File

@@ -0,0 +1,68 @@
import { UmbControllerHostElement, UmbControllerHostMixin } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextProviderController, UmbContextToken } from '@umbraco-cms/backoffice/context-api';
/**
* Provides a value to the context down the DOM tree.
*
* @remarks This element is a wrapper around the `provideContext` function.
* @slot - The context will be available to all descendants given in the default slot.
* @throws {Error} If the key property is not set.
* @throws {Error} If the value property is not set.
*/
export class UmbContextProviderElement extends UmbControllerHostMixin(HTMLElement) {
/**
* The value to provide to the context.
* @optional
*/
public create?: (host: UmbControllerHostElement) => unknown;
/**
* The value to provide to the context.
* @required
*/
public value: unknown;
/**
* The key to provide to the context.
* @required
*/
public key!: string | UmbContextToken;
static get observedAttributes() {
return ['value', 'key'];
}
attributeChangedCallback(name: string, _oldValue: string | UmbContextToken, newValue: string | UmbContextToken) {
if (name === 'key') this.key = newValue;
if (name === 'value') this.value = newValue;
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
const slot = document.createElement('slot');
this.shadowRoot?.appendChild(slot);
}
connectedCallback() {
super.connectedCallback();
if (!this.key) {
throw new Error('The key property is required.');
}
if (this.create) {
this.value = this.create(this);
} else if (!this.value) {
throw new Error('The value property is required.');
}
new UmbContextProviderController(this, this.key, this.value);
}
}
customElements.define('umb-context-provider', UmbContextProviderElement);
declare global {
interface HTMLElementTagNameMap {
'umb-context-provider': UmbContextProviderElement;
}
}

View File

@@ -0,0 +1,34 @@
import { UmbControllerHostElement, UmbControllerHostMixin } from './controller-host.mixin';
export class UmbControllerHostInitializerElement
extends UmbControllerHostMixin(HTMLElement)
implements UmbControllerHostElement
{
/**
* A way to initialize controllers.
* @required
*/
create?: (host: UmbControllerHostElement) => void;
constructor() {
super();
this.attachShadow({ mode: 'open' });
const slot = document.createElement('slot');
this.shadowRoot?.appendChild(slot);
}
connectedCallback() {
super.connectedCallback();
if (this.create) {
this.create(this);
}
}
}
customElements.define('umb-controller-host-initializer', UmbControllerHostInitializerElement);
declare global {
interface HTMLElementTagNameMap {
'umb-controller-host-initializer': UmbControllerHostInitializerElement;
}
}

View File

@@ -0,0 +1,44 @@
import { expect, fixture, html } from '@open-wc/testing';
import { customElement } from 'lit/decorators.js';
import { UmbControllerHostInitializerElement } from './controller-host-initializer.element';
import { UmbControllerHostElement, UmbControllerHostMixin } from './controller-host.mixin';
import { UmbContextConsumerController, UmbContextProviderController } from '@umbraco-cms/backoffice/context-api';
@customElement('umb-test-controller-host-initializer-consumer')
export class UmbTestControllerHostInitializerConsumerElement extends UmbControllerHostMixin(HTMLElement) {
public value: string | null = null;
constructor() {
super();
new UmbContextConsumerController<string>(this, 'my-test-context-alias', (value) => {
this.value = value;
});
}
}
describe('UmbControllerHostTestElement', () => {
let element: UmbControllerHostInitializerElement;
let consumer: UmbTestControllerHostInitializerConsumerElement;
const contextValue = 'test-value';
beforeEach(async () => {
element = await fixture(
html` <umb-controller-host-initializer
.create=${(host: UmbControllerHostElement) =>
new UmbContextProviderController(host, 'my-test-context-alias', contextValue)}>
<umb-test-controller-host-initializer-consumer></umb-test-controller-host-initializer-consumer>
</umb-controller-host-initializer>`
);
consumer = element.getElementsByTagName(
'umb-test-controller-host-initializer-consumer'
)[0] as UmbTestControllerHostInitializerConsumerElement;
});
it('element is defined with its own instance', () => {
expect(element).to.be.instanceOf(UmbControllerHostInitializerElement);
});
it('provides the context', () => {
expect(consumer.value).to.equal(contextValue);
});
});

View File

@@ -1,5 +1,6 @@
import { UmbControllerInterface } from './controller.interface';
import type { HTMLElementConstructor } from '@umbraco-cms/backoffice/models';
type HTMLElementConstructor<T = HTMLElement> = new (...args: any[]) => T;
export declare class UmbControllerHostElement extends HTMLElement {
hasController(controller: UmbControllerInterface): boolean;

View File

@@ -0,0 +1,25 @@
import { DataTypePropertyPresentationModel } from '@umbraco-cms/backoffice/backend-api';
/**
* Extends Array to add utility functions for accessing data type properties
* by alias, returning either the value or the complete DataTypePropertyPresentationModel object
*/
export class UmbDataTypePropertyCollection extends Array<DataTypePropertyPresentationModel> {
constructor(args: Array<DataTypePropertyPresentationModel> = []) {
super(...args);
}
getValueByAlias<T>(alias: string): T | undefined {
const property = this.getByAlias(alias);
if (property?.value === undefined || property?.value === null) {
return;
}
return property.value as T;
}
getByAlias(alias: string): DataTypePropertyPresentationModel | undefined {
return this.find((x) => x.alias === alias);
}
}

View File

@@ -0,0 +1 @@
export * from './data-type-property-collection.class';

View File

@@ -1,8 +1,6 @@
import { Observable } from 'rxjs';
import type { HTMLElementConstructor } from '@umbraco-cms/backoffice/models';
import { UmbControllerHostElement, UmbControllerHostMixin } from '@umbraco-cms/backoffice/controller';
import type { HTMLElementConstructor } from '@umbraco-cms/backoffice/extension-api';
import { UmbControllerHostElement, UmbControllerHostMixin } from '@umbraco-cms/backoffice/controller-api';
import {
UmbContextToken,
UmbContextCallback,

View File

@@ -1,5 +1,6 @@
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { umbExtensionsRegistry, createExtensionClass } from '@umbraco-cms/backoffice/extensions-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { createExtensionClass } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
export interface UmbAction<RepositoryType = unknown> {

View File

@@ -1,5 +1,5 @@
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export class UmbCopyEntityAction<T extends { copy(): Promise<void> }> extends UmbEntityActionBase<T> {
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {

View File

@@ -1,6 +1,6 @@
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
import { UmbFolderRepository } from '@umbraco-cms/backoffice/repository';

View File

@@ -1,6 +1,6 @@
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
import { UmbDetailRepository, UmbItemRepository } from '@umbraco-cms/backoffice/repository';

View File

@@ -1,6 +1,6 @@
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbModalContext, UMB_FOLDER_MODAL, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/modal';
import { UmbFolderRepository } from '@umbraco-cms/backoffice/repository';

View File

@@ -1,5 +1,5 @@
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
// TODO: investigate what we need to finish the generic move action. We would need to open a picker, which requires a modal token,
// maybe we can use kinds to make a specific manifest to the move action.

View File

@@ -1,5 +1,5 @@
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export class UmbSortChildrenOfEntityAction<
T extends { sortChildrenOf(): Promise<void> }

View File

@@ -1,6 +1,6 @@
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
import { UmbItemRepository } from '@umbraco-cms/backoffice/repository';

View File

@@ -1,5 +1,5 @@
import { UmbAction, UmbActionBase } from './action';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export interface UmbEntityAction<RepositoryType> extends UmbAction<RepositoryType> {
unique: string;

View File

@@ -1,5 +1,5 @@
import { UmbAction, UmbActionBase } from './action';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export interface UmbEntityBulkAction<RepositoryType = unknown> extends UmbAction<RepositoryType> {
selection: Array<string>;

View File

@@ -1,8 +1,7 @@
import { hasDefaultExport } from './has-default-export.function';
import { isManifestClassConstructorType } from './is-manifest-class-instance-type.function';
import { isManifestClassConstructorType } from './type-guards';
import { loadExtension } from './load-extension.function';
import type { ClassConstructor } from '@umbraco-cms/backoffice/models';
import type { ManifestClass } from '@umbraco-cms/backoffice/extensions-registry';
import type { ManifestClass, ClassConstructor } from './types';
//TODO: Write tests for this method:
export async function createExtensionClass<T = unknown>(

View File

@@ -1,7 +1,10 @@
import { createExtensionElement } from './create-extension-element.function';
import { isManifestElementableType } from './is-manifest-elementable-type.function';
import { isManifestElementableType } from './type-guards';
export async function createExtensionElementOrFallback(manifest: any, fallbackElementName: string): Promise<HTMLElement | undefined> {
export async function createExtensionElementOrFallback(
manifest: any,
fallbackElementName: string
): Promise<HTMLElement | undefined> {
if (isManifestElementableType(manifest)) {
return createExtensionElement(manifest);
}

View File

@@ -1,8 +1,7 @@
import { hasDefaultExport } from './has-default-export.function';
import { isManifestElementNameType } from './is-manifest-element-name-type.function';
import { isManifestElementNameType } from './type-guards';
import { loadExtension } from './load-extension.function';
import type { HTMLElementConstructor } from '@umbraco-cms/backoffice/models';
import type { ManifestElement } from '@umbraco-cms/backoffice/extensions-registry';
import type { HTMLElementConstructor, ManifestElement } from './types';
export async function createExtensionElement<ElementType extends HTMLElement>(
manifest: ManifestElement<ElementType>

View File

@@ -1,13 +1,15 @@
import type { ManifestEntrypoint } from './models';
import { hasInitExport, loadExtension, UmbExtensionRegistry } from '@umbraco-cms/backoffice/extensions-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import type { ManifestEntryPoint } from './types';
import { hasInitExport } from './has-init-export.function';
import { loadExtension } from './load-extension.function';
import { UmbExtensionRegistry } from './registry/extension.registry';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export class UmbEntryPointExtensionInitializer {
#host;
#extensionRegistry;
#entryPointMap = new Map();
constructor(host: UmbControllerHostElement, extensionRegistry: UmbExtensionRegistry) {
constructor(host: UmbControllerHostElement, extensionRegistry: UmbExtensionRegistry<ManifestEntryPoint>) {
this.#host = host;
this.#extensionRegistry = extensionRegistry;
extensionRegistry.extensionsOfType('entryPoint').subscribe((entryPoints) => {
@@ -19,7 +21,7 @@ export class UmbEntryPointExtensionInitializer {
});
}
async instantiateEntryPoint(manifest: ManifestEntrypoint) {
async instantiateEntryPoint(manifest: ManifestEntryPoint) {
const js = await loadExtension(manifest);
// If the extension has an onInit export, be sure to run that or else let the module handle itself
if (hasInitExport(js)) {

View File

@@ -1,8 +1,8 @@
import type { UmbEntrypointModule } from './umb-lifecycle.interface';
import type { UmbEntryPointModule } from './umb-lifecycle.interface';
/**
* Validate if an ESModule exports a known init function called 'onInit'
*/
export function hasInitExport(obj: unknown): obj is Pick<UmbEntrypointModule, 'onInit'> {
export function hasInitExport(obj: unknown): obj is Pick<UmbEntryPointModule, 'onInit'> {
return obj !== null && typeof obj === 'object' && 'onInit' in obj;
}

View File

@@ -1,16 +1,11 @@
import { UmbExtensionRegistry } from './registry/extension.registry';
export * from './registry/extension.registry';
export * from './type-guards';
export * from './create-extension-element.function';
export * from './has-default-export.function';
export * from './has-init-export.function';
export * from './is-manifest-element-name-type.function';
export * from './is-manifest-elementable-type.function';
export * from './is-manifest-js-type.function';
export * from './is-manifest-loader-type.function';
export * from './load-extension.function';
export * from './create-extension-element-or-fallback.function';
export * from './create-extension-class.function';
export * from './umb-lifecycle.interface';
export const umbExtensionsRegistry = new UmbExtensionRegistry();
export * from './entry-point-extension-initializer';
export * from './types';

View File

@@ -1,6 +1,5 @@
import { isManifestJSType } from './is-manifest-js-type.function';
import { isManifestLoaderType } from './is-manifest-loader-type.function';
import type { ManifestWithLoader } from '@umbraco-cms/backoffice/extensions-registry';
import { isManifestJSType, isManifestLoaderType } from './type-guards';
import type { ManifestWithLoader } from './types';
export async function loadExtension<T = unknown>(manifest: ManifestWithLoader<T>): Promise<T | null> {
try {

View File

@@ -1,10 +1,10 @@
import { expect } from '@open-wc/testing';
import type { ManifestElementWithElementName, ManifestKind, ManifestWithMeta } from '../types';
import { UmbExtensionRegistry } from './extension.registry';
import type { ManifestKind, ManifestTypes } from '@umbraco-cms/backoffice/extensions-registry';
describe('UmbExtensionRegistry', () => {
let extensionRegistry: UmbExtensionRegistry;
let manifests: Array<ManifestTypes>;
let extensionRegistry: UmbExtensionRegistry<ManifestWithMeta>;
let manifests: Array<ManifestWithMeta>;
beforeEach(() => {
extensionRegistry = new UmbExtensionRegistry();
@@ -152,11 +152,13 @@ describe('UmbExtensionRegistry', () => {
});
describe('UmbExtensionRegistry with kinds', () => {
let extensionRegistry: UmbExtensionRegistry;
let manifests: Array<ManifestTypes | ManifestKind>;
let extensionRegistry: UmbExtensionRegistry<any>;
let manifests: Array<
ManifestElementWithElementName | ManifestWithMeta | ManifestKind<ManifestElementWithElementName>
>;
beforeEach(() => {
extensionRegistry = new UmbExtensionRegistry();
extensionRegistry = new UmbExtensionRegistry<any>();
manifests = [
{
type: 'kind',

View File

@@ -1,12 +1,5 @@
import { BehaviorSubject, map, Observable, distinctUntilChanged, combineLatest } from 'rxjs';
import type {
ManifestTypes,
ManifestTypeMap,
ManifestBase,
SpecificManifestTypeOrManifestBase,
ManifestKind,
} from '@umbraco-cms/backoffice/extensions-registry';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { ManifestTypeMap, ManifestBase, SpecificManifestTypeOrManifestBase, ManifestKind } from '../types';
function extensionArrayMemoization<T extends { alias: string }>(
previousValue: Array<T>,
@@ -35,27 +28,34 @@ function extensionSingleMemoization<T extends { alias: string }>(
const sortExtensions = (a: ManifestBase, b: ManifestBase) => (b.weight || 0) - (a.weight || 0);
export class UmbExtensionRegistry {
export class UmbExtensionRegistry<
IncomingManifestTypes extends ManifestBase,
ManifestTypes extends ManifestBase = IncomingManifestTypes | ManifestBase
> {
// TODO: Use UniqueBehaviorSubject, as we don't want someone to edit data of extensions.
private _extensions = new BehaviorSubject<Array<ManifestTypes>>([]);
public readonly extensions = this._extensions.asObservable();
private _kinds = new BehaviorSubject<Array<ManifestKind>>([]);
private _kinds = new BehaviorSubject<Array<ManifestKind<ManifestTypes>>>([]);
public readonly kinds = this._kinds.asObservable();
defineKind(kind: ManifestKind) {
defineKind(kind: ManifestKind<ManifestTypes>) {
const nextData = this._kinds
.getValue()
.filter(
(k) => !(k.matchType === (kind as ManifestKind).matchType && k.matchKind === (kind as ManifestKind).matchKind)
(k) =>
!(
k.matchType === (kind as ManifestKind<ManifestTypes>).matchType &&
k.matchKind === (kind as ManifestKind<ManifestTypes>).matchKind
)
);
nextData.push(kind as ManifestKind);
nextData.push(kind as ManifestKind<ManifestTypes>);
this._kinds.next(nextData);
}
register(manifest: ManifestTypes | ManifestKind): void {
register(manifest: ManifestTypes | ManifestKind<ManifestTypes>): void {
if (manifest.type === 'kind') {
this.defineKind(manifest as ManifestKind);
this.defineKind(manifest as ManifestKind<ManifestTypes>);
return;
}
@@ -70,7 +70,7 @@ export class UmbExtensionRegistry {
this._extensions.next([...extensionsValues, manifest as ManifestTypes]);
}
registerMany(manifests: Array<ManifestTypes>): void {
registerMany(manifests: Array<ManifestTypes | ManifestKind<ManifestTypes>>): void {
manifests.forEach((manifest) => this.register(manifest));
}
@@ -99,13 +99,13 @@ export class UmbExtensionRegistry {
}
*/
private _kindsOfType<Key extends keyof ManifestTypeMap | string>(type: Key) {
private _kindsOfType<Key extends keyof ManifestTypeMap<ManifestTypes> | string>(type: Key) {
return this.kinds.pipe(
map((kinds) => kinds.filter((kind) => kind.matchType === type)),
distinctUntilChanged(extensionArrayMemoization)
);
}
private _extensionsOfType<Key extends keyof ManifestTypeMap | string>(type: Key) {
private _extensionsOfType<Key extends keyof ManifestTypeMap<ManifestTypes> | string>(type: Key) {
return this.extensions.pipe(
map((exts) => exts.filter((ext) => ext.type === type)),
distinctUntilChanged(extensionArrayMemoization)
@@ -117,16 +117,20 @@ export class UmbExtensionRegistry {
distinctUntilChanged(extensionArrayMemoization)
);
}
private _extensionsOfTypes<ExtensionType = ManifestBase>(types: string[]): Observable<Array<ExtensionType>> {
// TODO: can we get rid of as unknown here
private _extensionsOfTypes<ExtensionType extends ManifestBase = ManifestBase>(
types: Array<ExtensionType['type']>
): Observable<Array<ExtensionType>> {
return this.extensions.pipe(
map((exts) => exts.filter((ext) => types.indexOf(ext.type) !== -1)),
distinctUntilChanged(extensionArrayMemoization)
) as Observable<Array<ExtensionType>>;
) as unknown as Observable<Array<ExtensionType>>;
}
getByTypeAndAlias<
Key extends keyof ManifestTypeMap | string,
T extends ManifestBase = SpecificManifestTypeOrManifestBase<Key>
Key extends keyof ManifestTypeMap<ManifestTypes> | string,
T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>
>(type: Key, alias: string) {
return combineLatest([
this.extensions.pipe(
@@ -155,8 +159,8 @@ export class UmbExtensionRegistry {
}
extensionsOfType<
Key extends keyof ManifestTypeMap | string,
T extends ManifestBase = SpecificManifestTypeOrManifestBase<Key>
Key extends keyof ManifestTypeMap<ManifestTypes> | string,
T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>
>(type: Key) {
return combineLatest([this._extensionsOfType(type), this._kindsOfType(type)]).pipe(
map(([exts, kinds]) =>
@@ -205,5 +209,3 @@ export class UmbExtensionRegistry {
) as Observable<Array<ExtensionTypes>>;
}
}
export const UMB_EXTENSION_REGISTRY_TOKEN = new UmbContextToken<UmbExtensionRegistry>('UmbExtensionRegistry');

View File

@@ -0,0 +1,6 @@
export * from './is-manifest-class-instance-type.function';
export * from './is-manifest-classable-type.function';
export * from './is-manifest-element-name-type.function';
export * from './is-manifest-elementable-type.function';
export * from './is-manifest-js-type.function';
export * from './is-manifest-loader-type.function';

View File

@@ -1,4 +1,4 @@
import type { ManifestClass, ManifestClassWithClassConstructor } from '@umbraco-cms/backoffice/extensions-registry';
import type { ManifestClass, ManifestClassWithClassConstructor } from '../types';
export function isManifestClassConstructorType(manifest: unknown): manifest is ManifestClassWithClassConstructor {
return typeof manifest === 'object' && manifest !== null && (manifest as ManifestClass).class !== undefined;

View File

@@ -1,8 +1,12 @@
import type { ManifestBase, ManifestClass } from '../types';
import { isManifestJSType } from './is-manifest-js-type.function';
import { isManifestLoaderType } from './is-manifest-loader-type.function';
import { isManifestClassConstructorType } from './is-manifest-class-instance-type.function';
import type { ManifestBase, ManifestClass } from '@umbraco-cms/backoffice/extensions-registry';
export function isManifestClassableType(manifest: ManifestBase): manifest is ManifestClass {
return isManifestClassConstructorType(manifest) || isManifestLoaderType<object>(manifest) || isManifestJSType<object>(manifest);
return (
isManifestClassConstructorType(manifest) ||
isManifestLoaderType<object>(manifest) ||
isManifestJSType<object>(manifest)
);
}

View File

@@ -1,4 +1,4 @@
import type { ManifestElement, ManifestElementWithElementName } from '@umbraco-cms/backoffice/extensions-registry';
import type { ManifestElement, ManifestElementWithElementName } from '../types';
export function isManifestElementNameType(manifest: unknown): manifest is ManifestElementWithElementName {
return typeof manifest === 'object' && manifest !== null && (manifest as ManifestElement).elementName !== undefined;

View File

@@ -1,9 +1,11 @@
import type { ManifestElement, ManifestBase } from '../types';
import { isManifestElementNameType } from './is-manifest-element-name-type.function';
import { isManifestJSType } from './is-manifest-js-type.function';
import { isManifestLoaderType } from './is-manifest-loader-type.function';
import type { ManifestElement, ManifestBase } from '@umbraco-cms/backoffice/extensions-registry';
export function isManifestElementableType<ElementType extends HTMLElement = HTMLElement>(manifest: ManifestBase): manifest is ManifestElement {
export function isManifestElementableType<ElementType extends HTMLElement = HTMLElement>(
manifest: ManifestBase
): manifest is ManifestElement {
return (
isManifestElementNameType(manifest) ||
isManifestLoaderType<ElementType>(manifest) ||

View File

@@ -1,4 +1,4 @@
import type { ManifestBase, ManifestWithLoader } from '@umbraco-cms/backoffice/extensions-registry';
import type { ManifestBase, ManifestWithLoader } from '../types';
export type ManifestJSType<T> = ManifestWithLoader<T> & { js: string };
export function isManifestJSType<T>(manifest: ManifestBase | unknown): manifest is ManifestJSType<T> {

View File

@@ -1,8 +1,9 @@
import type { ManifestBase, ManifestWithLoader } from '../types';
import type { ManifestBase, ManifestWithLoader } from '@umbraco-cms/backoffice/extensions-registry';
export type ManifestLoaderType<T> = ManifestWithLoader<T> & {
loader: () => Promise<T>;
};
export function isManifestLoaderType<T>(manifest: ManifestBase): manifest is ManifestLoaderType<T> {
return typeof (manifest as ManifestLoaderType<T>).loader === 'function';
}

View File

@@ -0,0 +1,159 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type HTMLElementConstructor<T = HTMLElement> = new (...args: any[]) => T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ClassConstructor<T> = new (...args: any[]) => T;
export type ManifestTypeMap<ManifestTypes extends ManifestBase> = {
[Manifest in ManifestTypes as Manifest['type']]: Manifest;
} & {
[key: string]: ManifestBase;
};
export type SpecificManifestTypeOrManifestBase<
ManifestTypes extends ManifestBase,
T extends keyof ManifestTypeMap<ManifestTypes> | string
> = T extends keyof ManifestTypeMap<ManifestTypes> ? ManifestTypeMap<ManifestTypes>[T] : ManifestBase;
export interface ManifestBase {
/**
* The type of extension such as dashboard etc...
*/
type: string;
/**
* The alias of the extension, ensure it is unique
*/
alias: string;
/**
* The kind of the extension, used to group extensions together
*
* @examples ["button"]
*/
kind?: unknown; // I had to add the optional kind property set to undefined. To make the ManifestTypes recognize the Manifest Kind types. Notice that Kinds has to Omit the kind property when extending.
/**
* The friendly name of the extension
*/
name: string;
/**
* Extensions such as dashboards are ordered by weight with lower numbers being first in the list
*/
weight?: number;
}
export interface ManifestKind<ManifestTypes> {
type: 'kind';
alias: string;
matchType: string;
matchKind: string;
manifest: Partial<ManifestTypes>;
}
export interface ManifestWithConditions<ConditionsType> {
/**
* Set the conditions for when the extension should be loaded
*/
conditions: ConditionsType;
}
export interface ManifestWithLoader<LoaderReturnType> extends ManifestBase {
/**
* @TJS-ignore
*/
loader?: () => Promise<LoaderReturnType>;
}
/**
* The type of extension such as dashboard etc...
*/
export interface ManifestClass<ClassType = unknown>
extends ManifestWithLoader<{ default: ClassConstructor<ClassType> }> {
readonly CLASS_TYPE?: ClassType;
/**
* The file location of the javascript file to load
* @TJS-required
*/
js?: string;
/**
* @TJS-ignore
*/
className?: string;
/**
* @TJS-ignore
*/
class?: ClassConstructor<ClassType>;
//loader?: () => Promise<object | HTMLElement>;
}
export interface ManifestClassWithClassConstructor<T = unknown> extends ManifestClass<T> {
class: ClassConstructor<T>;
}
export interface ManifestElement<ElementType extends HTMLElement = HTMLElement>
extends ManifestWithLoader<{ default: ClassConstructor<ElementType> } | Omit<object, 'default'>> {
readonly ELEMENT_TYPE?: ElementType;
/**
* The file location of the javascript file to load
*
* @TJS-require
*/
js?: string;
/**
* The HTML web component name to use such as 'my-dashboard'
* Note it is NOT <my-dashboard></my-dashboard> but just the name
*/
elementName?: string;
//loader?: () => Promise<object | HTMLElement>;
/**
* This contains properties specific to the type of extension
*/
meta?: unknown;
}
export interface ManifestWithView<ElementType extends HTMLElement = HTMLElement> extends ManifestElement<ElementType> {
meta: MetaManifestWithView;
}
export interface MetaManifestWithView {
pathname: string;
label: string;
icon: string;
}
export interface ManifestElementWithElementName extends ManifestElement {
/**
* The HTML web component name to use such as 'my-dashboard'
* Note it is NOT <my-dashboard></my-dashboard> but just the name
*/
elementName: string;
}
export interface ManifestWithMeta extends ManifestBase {
/**
* This contains properties specific to the type of extension
*/
meta: unknown;
}
/**
* This type of extension gives full control and will simply load the specified JS file
* You could have custom logic to decide which extensions to load/register by using extensionRegistry
*/
export interface ManifestEntryPoint extends ManifestBase {
type: 'entryPoint';
/**
* The file location of the javascript file to load in the backoffice
*/
js: string;
}

View File

@@ -0,0 +1,15 @@
import type { UmbExtensionRegistry } from './registry/extension.registry';
import { ManifestBase } from './types';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export type UmbEntryPointOnInit = (
host: UmbControllerHostElement,
extensionRegistry: UmbExtensionRegistry<ManifestBase>
) => void;
/**
* Interface containing supported life-cycle functions for ESModule entry points
*/
export interface UmbEntryPointModule {
onInit: UmbEntryPointOnInit;
}

View File

@@ -1,3 +1,3 @@
export * from './interfaces';
export * from './models';
export * from './entry-point-extension-initializer';
export * from './registry';

View File

@@ -0,0 +1,14 @@
import type { ManifestModal } from '../models';
import type { UmbModalHandler } from '@umbraco-cms/backoffice/modal';
export interface UmbModalExtensionElement<
UmbModalData extends object = object,
UmbModalResult = unknown,
ModalManifestType extends ManifestModal = ManifestModal
> extends HTMLElement {
manifest?: ModalManifestType;
modalHandler?: UmbModalHandler<UmbModalData, UmbModalResult>;
data?: UmbModalData;
}

View File

@@ -0,0 +1,6 @@
import { UmbDataTypePropertyCollection } from '@umbraco-cms/backoffice/data-type';
export interface UmbPropertyEditorExtensionElement extends HTMLElement {
value: unknown;
config?: UmbDataTypePropertyCollection;
}

View File

@@ -1,4 +1,4 @@
import type { ManifestElement, ManifestWithConditions } from '.';
import type { ManifestElement, ManifestWithConditions } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestCollectionView extends ManifestElement, ManifestWithConditions<ConditionsCollectionView> {
type: 'collectionView';

View File

@@ -1,4 +1,4 @@
import type { ManifestBase } from '.';
import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestDashboardCollection extends ManifestBase {
type: 'dashboardCollection';

View File

@@ -1,5 +1,5 @@
import type { UmbDashboardExtensionElement } from '../interfaces';
import type { ManifestElement, ManifestWithConditions } from '.';
import type { ManifestElement, ManifestWithConditions } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestDashboard
extends ManifestElement<UmbDashboardExtensionElement>,

View File

@@ -1,4 +1,4 @@
import type { ManifestElement } from '.';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
/**
* An action to perform on an entity

View File

@@ -1,4 +1,4 @@
import type { ManifestElement, ManifestWithConditions } from '.';
import type { ManifestElement, ManifestWithConditions } from '@umbraco-cms/backoffice/extension-api';
/**
* An action to perform on multiple entities

View File

@@ -1,5 +1,5 @@
import type { UmbExternalLoginProviderExtensionElement } from '../interfaces/external-login-provider-extension-element.interface';
import type { ManifestElement } from '.';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestExternalLoginProvider extends ManifestElement<UmbExternalLoginProviderExtensionElement> {
type: 'externalLoginProvider';

View File

@@ -1,4 +1,4 @@
import type { ManifestElement } from '.';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
/**
* Header apps are displayed in the top right corner of the backoffice

View File

@@ -1,4 +1,4 @@
import type { ManifestElement } from '.';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestHealthCheck extends ManifestElement {
type: 'healthCheck';

View File

@@ -0,0 +1,93 @@
import type { ManifestCollectionView } from './collection-view.model';
import type { ManifestDashboard } from './dashboard.model';
import type { ManifestDashboardCollection } from './dashboard-collection.model';
import type { ManifestEntityAction } from './entity-action.model';
import type { ManifestEntityBulkAction } from './entity-bulk-action.model';
import type { ManifestExternalLoginProvider } from './external-login-provider.model';
import type { ManifestHeaderApp, ManifestHeaderAppButtonKind } from './header-app.model';
import type { ManifestHealthCheck } from './health-check.model';
import type { ManifestMenu } from './menu.model';
import type { ManifestMenuItem, ManifestMenuItemTreeKind } from './menu-item.model';
import type { ManifestModal } from './modal.model';
import type { ManifestPackageView } from './package-view.model';
import type { ManifestPropertyAction } from './property-action.model';
import type { ManifestPropertyEditorUI, ManifestPropertyEditorModel } from './property-editor.model';
import type { ManifestRepository } from './repository.model';
import type { ManifestSection } from './section.model';
import type { ManifestSectionSidebarApp, ManifestSectionSidebarAppMenuKind } from './section-sidebar-app.model';
import type { ManifestSectionView } from './section-view.model';
import type { ManifestStore, ManifestTreeStore, ManifestItemStore } from './store.model';
import type { ManifestTheme } from './theme.model';
import type { ManifestTree } from './tree.model';
import type { ManifestTreeItem } from './tree-item.model';
import type { ManifestUserProfileApp } from './user-profile-app.model';
import type { ManifestWorkspace } from './workspace.model';
import type { ManifestWorkspaceAction } from './workspace-action.model';
import type { ManifestWorkspaceEditorView } from './workspace-editor-view.model';
import type { ManifestWorkspaceViewCollection } from './workspace-view-collection.model';
import type { ManifestBase, ManifestEntryPoint } from '@umbraco-cms/backoffice/extension-api';
export * from './collection-view.model';
export * from './dashboard-collection.model';
export * from './dashboard.model';
export * from './entity-action.model';
export * from './entity-bulk-action.model';
export * from './external-login-provider.model';
export * from './header-app.model';
export * from './health-check.model';
export * from './menu-item.model';
export * from './menu.model';
export * from './modal.model';
export * from './package-view.model';
export * from './property-action.model';
export * from './property-editor.model';
export * from './repository.model';
export * from './section-sidebar-app.model';
export * from './section-view.model';
export * from './section.model';
export * from './store.model';
export * from './theme.model';
export * from './tree-item.model';
export * from './tree.model';
export * from './user-profile-app.model';
export * from './workspace-action.model';
export * from './workspace-view-collection.model';
export * from './workspace-editor-view.model';
export * from './workspace.model';
export type ManifestTypes =
| ManifestCollectionView
| ManifestDashboard
| ManifestDashboardCollection
| ManifestEntityAction
| ManifestEntityBulkAction
| ManifestEntryPoint
| ManifestExternalLoginProvider
| ManifestHeaderApp
| ManifestHeaderAppButtonKind
| ManifestHealthCheck
| ManifestItemStore
| ManifestMenu
| ManifestMenuItem
| ManifestMenuItemTreeKind
| ManifestModal
| ManifestPackageView
| ManifestPropertyAction
| ManifestPropertyEditorModel
| ManifestPropertyEditorUI
| ManifestRepository
| ManifestSection
| ManifestSectionSidebarApp
| ManifestSectionSidebarAppMenuKind
| ManifestSectionView
| ManifestStore
| ManifestTheme
| ManifestTree
| ManifestTreeItem
| ManifestTreeStore
| ManifestUserProfileApp
| ManifestWorkspace
| ManifestWorkspaceAction
| ManifestWorkspaceEditorView
| ManifestWorkspaceViewCollection
| ManifestBase;

View File

@@ -1,5 +1,5 @@
import type { UmbMenuItemExtensionElement } from '../interfaces/menu-item-extension-element.interface';
import type { ManifestElement } from '.';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestMenuItem extends ManifestElement<UmbMenuItemExtensionElement> {
type: 'menuItem';

View File

@@ -0,0 +1,5 @@
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestMenu extends ManifestElement {
type: 'menu';
}

View File

@@ -0,0 +1,5 @@
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestModal extends ManifestElement {
type: 'modal';
}

View File

@@ -1,4 +1,4 @@
import type { ManifestElement } from '.';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestPackageView extends ManifestElement {
type: 'packageView';

View File

@@ -1,4 +1,4 @@
import type { ManifestElement, ManifestWithConditions } from '.';
import type { ManifestElement, ManifestWithConditions } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestPropertyAction extends ManifestElement, ManifestWithConditions<ConditionsPropertyAction> {
type: 'propertyAction';

View File

@@ -1,5 +1,5 @@
import type { UmbPropertyEditorExtensionElement } from '../interfaces';
import type { ManifestElement, ManifestBase } from '.';
import type { ManifestElement, ManifestBase } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestPropertyEditorUI extends ManifestElement<UmbPropertyEditorExtensionElement> {
type: 'propertyEditorUI';

View File

@@ -1,5 +1,4 @@
import type { ManifestClass } from '.';
import type { ManifestClass } from '@umbraco-cms/backoffice/extension-api';
// TODO: Consider adding a ClassType for this manifest. (Currently we cannot know the scope of a repository, therefor we are going with unknown for now.)
export interface ManifestRepository extends ManifestClass<unknown> {
type: 'repository';

View File

@@ -1,5 +1,5 @@
import type { UmbSectionSidebarAppExtensionElement } from '../interfaces/section-sidebar-app-extension-element.interface';
import type { ManifestElement } from '.';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestSectionSidebarApp extends ManifestElement<UmbSectionSidebarAppExtensionElement> {
type: 'sectionSidebarApp';

View File

@@ -1,5 +1,5 @@
import type { UmbSectionViewExtensionElement } from '../interfaces/section-view-extension-element.interface';
import type { ManifestElement, ManifestWithConditions } from '.';
import type { ManifestElement, ManifestWithConditions } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestSectionView
extends ManifestElement<UmbSectionViewExtensionElement>,

View File

@@ -1,5 +1,5 @@
import type { UmbSectionExtensionElement } from '../interfaces';
import type { ManifestElement } from '.';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestSection extends ManifestElement<UmbSectionExtensionElement> {
type: 'section';

View File

@@ -1,4 +1,4 @@
import type { ManifestClass } from '.';
import type { ManifestClass } from '@umbraco-cms/backoffice/extension-api';
import { UmbItemStore, UmbStoreBase, UmbTreeStore } from '@umbraco-cms/backoffice/store';
export interface ManifestStore extends ManifestClass<UmbStoreBase> {

View File

@@ -1,4 +1,4 @@
import type { ManifestWithLoader } from '.';
import type { ManifestWithLoader } from '@umbraco-cms/backoffice/extension-api';
// TODO: make or find type for JS Module with default export: Would be nice to support css file directly.

View File

@@ -1,5 +1,5 @@
import { UmbTreeItemExtensionElement } from '../interfaces';
import type { ManifestElement } from '.';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestTreeItem extends ManifestElement<UmbTreeItemExtensionElement> {
type: 'treeItem';

View File

@@ -1,4 +1,4 @@
import type { ManifestBase } from '.';
import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestTree extends ManifestBase {
type: 'tree';

Some files were not shown because too many files have changed in this diff Show More