@@ -139,8 +140,8 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
color: 'danger',
confirmLabel: 'Delete',
});
- modalHandler?.onClose().then(({ confirmed }) => {
- if (confirmed) this._removeRedirect(data);
+ modalHandler?.onSubmit().then(() => {
+ this._removeRedirect(data);
});
}
@@ -157,14 +158,14 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
}
private _disableRedirectHandler() {
- const modalHandler = this._modalContext?.confirm({
+ const modalHandler = this._modalContext?.open(UMB_CONFIRM_MODAL_TOKEN, {
headline: 'Disable URL tracker',
content: html`Are you sure you want to disable the URL tracker?`,
color: 'danger',
confirmLabel: 'Disable',
});
- modalHandler?.onClose().then(({ confirmed }) => {
- if (confirmed) this._toggleRedirect();
+ modalHandler?.onSubmit().then(() => {
+ this._toggleRedirect();
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts
index 1e27078ee3..c847dbb2cb 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts
@@ -4,32 +4,25 @@ import { ArrayState } from '@umbraco-cms/observable-api';
import { UmbStoreBase } from '@umbraco-cms/store';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
-
-export const UMB_DocumentBlueprint_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken
('UmbDocumentBlueprintDetailStore');
-
-
/**
* @export
- * @class UmbDocumentBlueprintDetailStore
+ * @class UmbDocumentBlueprintStore
* @extends {UmbStoreBase}
- * @description - Details Data Store for Document Blueprints
+ * @description - Data Store for Document Blueprints
*/
-export class UmbDocumentBlueprintDetailStore extends UmbStoreBase {
-
-
+export class UmbDocumentBlueprintStore extends UmbStoreBase {
// TODO: use the right type:
#data = new ArrayState([], (x) => x.key);
-
constructor(host: UmbControllerHostInterface) {
- super(host, UMB_DocumentBlueprint_DETAIL_STORE_CONTEXT_TOKEN.toString());
+ super(host, UMB_DOCUMENT_BLUEPRINT_STORE_CONTEXT_TOKEN.toString());
}
/**
* @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable.
* @param {string} key
* @return {*} {(Observable)}
- * @memberof UmbDocumentBlueprintDetailStore
+ * @memberof UmbDocumentBlueprintStore
*/
getByKey(key: string) {
// TODO: use backend cli when available.
@@ -39,21 +32,18 @@ export class UmbDocumentBlueprintDetailStore extends UmbStoreBase {
this.#data.append(data);
});
- return this.#data.getObservablePart((documents) =>
- documents.find((document) => document.key === key)
- );
+ return this.#data.getObservablePart((documents) => documents.find((document) => document.key === key));
}
getScaffold(entityType: string, parentKey: string | null) {
- return {
- } as DocumentBlueprintDetails;
+ return {} as DocumentBlueprintDetails;
}
// TODO: make sure UI somehow can follow the status of this action.
/**
* @description - Save a DocumentBlueprint.
* @param {Array} Dictionaries
- * @memberof UmbDocumentBlueprintDetailStore
+ * @memberof UmbDocumentBlueprintStore
* @return {*} {Promise}
*/
save(data: DocumentBlueprintDetails[]) {
@@ -86,7 +76,7 @@ export class UmbDocumentBlueprintDetailStore extends UmbStoreBase {
/**
* @description - Delete a Data Type.
* @param {string[]} keys
- * @memberof UmbDocumentBlueprintDetailStore
+ * @memberof UmbDocumentBlueprintStore
* @return {*} {Promise}
*/
async delete(keys: string[]) {
@@ -102,3 +92,7 @@ export class UmbDocumentBlueprintDetailStore extends UmbStoreBase {
this.#data.remove(keys);
}
}
+
+export const UMB_DOCUMENT_BLUEPRINT_STORE_CONTEXT_TOKEN = new UmbContextToken(
+ 'UmbDocumentBlueprintStore'
+);
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.tree.store.ts
index 89759657aa..08aa3e6b3e 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.tree.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.tree.store.ts
@@ -1,11 +1,8 @@
-import { DocumentBlueprintResource, DocumentTreeItemModel } from '@umbraco-cms/backend-api';
-import { tryExecuteAndNotify } from '@umbraco-cms/resources';
import { UmbContextToken } from '@umbraco-cms/context-api';
-import { ArrayState } from '@umbraco-cms/observable-api';
-import { UmbStoreBase } from '@umbraco-cms/store';
+import { UmbTreeStoreBase } from '@umbraco-cms/store';
import type { UmbControllerHostInterface } from '@umbraco-cms/controller';
-export const UMB_DocumentBlueprint_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken(
+export const UMB_DOCUMENT_BLUEPRINT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken(
'UmbDocumentBlueprintTreeStore'
);
@@ -15,81 +12,8 @@ export const UMB_DocumentBlueprint_TREE_STORE_CONTEXT_TOKEN = new UmbContextToke
* @extends {UmbStoreBase}
* @description - Tree Data Store for Document Blueprints
*/
-export class UmbDocumentBlueprintTreeStore extends UmbStoreBase {
- #data = new ArrayState([], (x) => x.key);
-
+export class UmbDocumentBlueprintTreeStore extends UmbTreeStoreBase {
constructor(host: UmbControllerHostInterface) {
- super(host, UMB_DocumentBlueprint_TREE_STORE_CONTEXT_TOKEN.toString());
- }
-
- // TODO: How can we avoid having this in both stores?
- /**
- * @description - Delete a Document Blueprint Type.
- * @param {string[]} keys
- * @memberof UmbDocumentBlueprintsStore
- * @return {*} {Promise}
- */
- async delete(keys: string[]) {
- // TODO: use backend cli when available.
- await fetch('/umbraco/backoffice/data-type/delete', {
- method: 'POST',
- body: JSON.stringify(keys),
- headers: {
- 'Content-Type': 'application/json',
- },
- });
-
- this.#data.remove(keys);
- }
-
- getTreeRoot() {
- tryExecuteAndNotify(this._host, DocumentBlueprintResource.getTreeDocumentBlueprintRoot({})).then(({ data }) => {
- if (data) {
- // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)?
- this.#data.append(data.items);
- }
- });
-
- // TODO: how do we handle trashed items?
- // TODO: remove ignore when we know how to handle trashed items.
- return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null && !item.isTrashed));
- }
-
- getTreeItemChildren(key: string) {
- /*
- tryExecuteAndNotify(
- this._host,
- DocumentBlueprintResource.getTreeDocumentBlueprintChildren({
- parentKey: key,
- })
- ).then(({ data }) => {
- if (data) {
- // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)?
- this.#data.append(data.items);
- }
- });
- */
-
- // TODO: how do we handle trashed items?
- // TODO: remove ignore when we know how to handle trashed items.
- return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === key && !item.isTrashed));
- }
-
- getTreeItems(keys: Array) {
- if (keys?.length > 0) {
- tryExecuteAndNotify(
- this._host,
- DocumentBlueprintResource.getTreeDocumentBlueprintItem({
- key: keys,
- })
- ).then(({ data }) => {
- if (data) {
- // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)?
- this.#data.append(data);
- }
- });
- }
-
- return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? '')));
+ super(host, UMB_DOCUMENT_BLUEPRINT_TREE_STORE_CONTEXT_TOKEN.toString());
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/manifests.ts
index 5bee1cd866..4b80c03dbb 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/manifests.ts
@@ -1,4 +1,24 @@
+import { UmbDocumentBlueprintStore } from './document-blueprint.detail.store';
+import { UmbDocumentBlueprintTreeStore } from './document-blueprint.tree.store';
import { manifests as menuItemManifests } from './menu-item/manifests';
import { manifests as workspaceManifests } from './workspace/manifests';
+import type { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
-export const manifests = [...menuItemManifests, ...workspaceManifests];
+export const DOCUMENT_BLUEPRINT_STORE_ALIAS = 'Umb.Store.DocumentBlueprint';
+export const DOCUMENT_BLUEPRINT_TREE_STORE_ALIAS = 'Umb.Store.DocumentBlueprintTree';
+
+const store: ManifestStore = {
+ type: 'store',
+ alias: DOCUMENT_BLUEPRINT_STORE_ALIAS,
+ name: 'Document Blueprint Store',
+ class: UmbDocumentBlueprintStore,
+};
+
+const treeStore: ManifestTreeStore = {
+ type: 'treeStore',
+ alias: DOCUMENT_BLUEPRINT_TREE_STORE_ALIAS,
+ name: 'Document Blueprint Tree Store',
+ class: UmbDocumentBlueprintTreeStore,
+};
+
+export const manifests = [store, treeStore, ...menuItemManifests, ...workspaceManifests];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/manifests.ts
index 89c4abbb74..5a6bf52874 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/manifests.ts
@@ -1,7 +1,9 @@
import { UmbDocumentTypeRepository } from './document-type.repository';
-import { ManifestRepository } from 'libs/extensions-registry/repository.models';
+import { UmbDocumentTypeStore } from './document-type.store';
+import { UmbDocumentTypeTreeStore } from './document-type.tree.store';
+import { ManifestRepository, ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
-export const DOCUMENT_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.DocumentTypes';
+export const DOCUMENT_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.DocumentType';
const repository: ManifestRepository = {
type: 'repository',
@@ -10,4 +12,21 @@ const repository: ManifestRepository = {
class: UmbDocumentTypeRepository,
};
-export const manifests = [repository];
+export const DOCUMENT_TYPE_STORE_ALIAS = 'Umb.Store.DocumentType';
+export const DOCUMENT_TYPE_TREE_STORE_ALIAS = 'Umb.Store.DocumentTypeTree';
+
+const store: ManifestStore = {
+ type: 'store',
+ alias: DOCUMENT_TYPE_STORE_ALIAS,
+ name: 'Document Type Store',
+ class: UmbDocumentTypeStore,
+};
+
+const treeStore: ManifestTreeStore = {
+ type: 'treeStore',
+ alias: DOCUMENT_TYPE_TREE_STORE_ALIAS,
+ name: 'Document Type Tree Store',
+ class: UmbDocumentTypeTreeStore,
+};
+
+export const manifests = [repository, store, treeStore];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts
index fd5c9537ea..5e5a660c77 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts
@@ -3,6 +3,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import type { UmbWorkspaceEntityElement } from '../../../shared/components/workspace/workspace-entity-element.interface';
+import { UMB_ICON_PICKER_MODAL_TOKEN } from '../../../shared/modals/icon-picker';
import { UmbWorkspaceDocumentTypeContext } from './document-type-workspace.context';
import type { DocumentTypeModel } from '@umbraco-cms/backend-api';
import { UmbLitElement } from '@umbraco-cms/element';
@@ -86,10 +87,10 @@ export class UmbDocumentTypeWorkspaceElement extends UmbLitElement implements Um
}
private async _handleIconClick() {
- const modalHandler = this._modalContext?.iconPicker();
+ const modalHandler = this._modalContext?.open(UMB_ICON_PICKER_MODAL_TOKEN);
- modalHandler?.onClose().then((saved) => {
- if (saved) this._workspaceContext?.setIcon(saved.icon);
+ modalHandler?.onSubmit().then((saved) => {
+ if (saved.icon) this._workspaceContext?.setIcon(saved.icon);
// TODO save color ALIAS as well
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts
index 190b93bce4..3e48456f2d 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts
@@ -4,6 +4,7 @@ import { customElement, state } from 'lit/decorators.js';
import { UmbWorkspaceDocumentTypeContext } from '../../document-type-workspace.context';
import { UmbLitElement } from '@umbraco-cms/element';
import type { DocumentTypeModel } from '@umbraco-cms/backend-api';
+import '../../../../../shared/property-creator/property-creator.element.ts';
@customElement('umb-workspace-view-document-type-design')
export class UmbWorkspaceViewDocumentTypeDesignElement extends UmbLitElement {
@@ -12,6 +13,7 @@ export class UmbWorkspaceViewDocumentTypeDesignElement extends UmbLitElement {
css`
:host {
display: block;
+ margin: var(--uui-size-space-6);
padding: var(--uui-size-space-6);
}
`,
@@ -41,7 +43,12 @@ export class UmbWorkspaceViewDocumentTypeDesignElement extends UmbLitElement {
}
render() {
- return html`Design of ${this._documentType?.name}`;
+ return html` Design of ${this._documentType?.name}
+
+
+
+
+ `;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create.action.ts
deleted file mode 100644
index d88cca15bb..0000000000
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create.action.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { UmbDocumentRepository } from '../repository/document.repository';
-import { UmbEntityActionBase } from '@umbraco-cms/entity-action';
-import { UmbControllerHostInterface } from '@umbraco-cms/controller';
-
-export class UmbCreateDocumentEntityAction extends UmbEntityActionBase {
- constructor(host: UmbControllerHostInterface, repositoryAlias: string, unique: string) {
- super(host, repositoryAlias, unique);
- }
-
- async execute() {
- console.log(`execute for: ${this.unique}`);
- alert('open create dialog');
- }
-}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create/create-document-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create/create-document-modal.element.ts
new file mode 100644
index 0000000000..e438a3e3e4
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create/create-document-modal.element.ts
@@ -0,0 +1,48 @@
+import { html } from 'lit';
+import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
+import { customElement } from 'lit/decorators.js';
+import { UmbCreateDocumentModalData, UmbCreateDocumentModalResultData } from '.';
+import { UmbModalBaseElement } from '@umbraco-cms/modal';
+
+@customElement('umb-create-document-modal')
+export class UmbCreateDocumentModalElement extends UmbModalBaseElement<
+ UmbCreateDocumentModalData,
+ UmbCreateDocumentModalResultData
+> {
+ static styles = [UUITextStyles];
+
+ private _handleCancel() {
+ this.modalHandler?.reject();
+ }
+
+ #onClick(event: PointerEvent) {
+ event.stopPropagation();
+ const target = event.target as HTMLButtonElement;
+ const documentType = target.value;
+ this.modalHandler?.submit({ documentType });
+ }
+
+ render() {
+ return html`
+
+ Render list of create options for ${this.data?.unique}
+
+
+
+
+
+
+
+ Cancel
+
+ `;
+ }
+}
+
+export default UmbCreateDocumentModalElement;
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-create-document-modal': UmbCreateDocumentModalElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create/create.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create/create.action.ts
new file mode 100644
index 0000000000..fcdcc7be3c
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create/create.action.ts
@@ -0,0 +1,35 @@
+import { UmbDocumentRepository } from '../../repository/document.repository';
+import type { UmbCreateDocumentModalResultData } from '.';
+import { UMB_CREATE_DOCUMENT_MODAL_TOKEN } from '.';
+import { UmbEntityActionBase } from '@umbraco-cms/entity-action';
+import { UmbControllerHostInterface } from '@umbraco-cms/controller';
+import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
+import { UmbContextConsumerController } from '@umbraco-cms/context-api';
+
+// TODO: temp import
+import './create-document-modal.element.ts';
+
+export class UmbCreateDocumentEntityAction extends UmbEntityActionBase {
+ #modalContext?: UmbModalContext;
+
+ constructor(host: UmbControllerHostInterface, repositoryAlias: string, unique: string) {
+ super(host, repositoryAlias, unique);
+
+ new UmbContextConsumerController(this.host, UMB_MODAL_CONTEXT_TOKEN, (instance) => {
+ this.#modalContext = instance;
+ });
+ }
+
+ async execute() {
+ // TODO: what to do if modal service is not available?
+ if (!this.#modalContext) return;
+
+ const modalHandler = this.#modalContext?.open(UMB_CREATE_DOCUMENT_MODAL_TOKEN, {
+ unique: this.unique,
+ });
+
+ // TODO: get type from modal result
+ const { documentType }: UmbCreateDocumentModalResultData = await modalHandler.onSubmit();
+ alert('create document with document type: ' + documentType);
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create/index.ts
new file mode 100644
index 0000000000..679f84e431
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/create/index.ts
@@ -0,0 +1,17 @@
+import { UmbModalToken } from '@umbraco-cms/modal';
+
+export interface UmbCreateDocumentModalData {
+ unique: string | null;
+}
+
+export interface UmbCreateDocumentModalResultData {
+ documentType: string;
+}
+
+export const UMB_CREATE_DOCUMENT_MODAL_TOKEN = new UmbModalToken<
+ UmbCreateDocumentModalData,
+ UmbCreateDocumentModalResultData
+>('Umb.Modal.CreateDocument', {
+ type: 'sidebar',
+ size: 'small',
+});
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/manifests.ts
index 7dc878c0b6..3ae95fe8ee 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-actions/manifests.ts
@@ -1,4 +1,5 @@
-import { UmbCreateDocumentEntityAction } from './create.action';
+import { DOCUMENT_REPOSITORY_ALIAS } from '../repository/manifests';
+import { UmbCreateDocumentEntityAction } from './create/create.action';
import { UmbPublishDocumentEntityAction } from './publish.action';
import { UmbDocumentCultureAndHostnamesEntityAction } from './culture-and-hostnames.action';
import { UmbCreateDocumentBlueprintEntityAction } from './create-blueprint.action';
@@ -12,10 +13,9 @@ import {
UmbTrashEntityAction,
UmbSortChildrenOfEntityAction,
} from '@umbraco-cms/entity-action';
-import { ManifestEntityAction } from '@umbraco-cms/extensions-registry';
+import { ManifestEntityAction, ManifestModal } from '@umbraco-cms/extensions-registry';
const entityType = 'document';
-const repositoryAlias = 'Umb.Repository.Documents';
const entityActions: Array = [
{
@@ -27,7 +27,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:add',
label: 'Create',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbCreateDocumentEntityAction,
},
},
@@ -40,7 +40,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:trash',
label: 'Trash',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbTrashEntityAction,
},
},
@@ -53,7 +53,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:blueprint',
label: 'Create Content Template',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbCreateDocumentBlueprintEntityAction,
},
},
@@ -66,7 +66,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:enter',
label: 'Move',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbMoveEntityAction,
},
},
@@ -79,7 +79,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:documents',
label: 'Copy',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbCopyEntityAction,
},
},
@@ -92,7 +92,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:navigation-vertical',
label: 'Sort',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbSortChildrenOfEntityAction,
},
},
@@ -105,7 +105,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:home',
label: 'Culture And Hostnames',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbDocumentCultureAndHostnamesEntityAction,
},
},
@@ -117,7 +117,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:vcard',
label: 'Permissions',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbDocumentPermissionsEntityAction,
},
},
@@ -129,7 +129,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:lock',
label: 'Public Access',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbDocumentPublicAccessEntityAction,
},
},
@@ -141,19 +141,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:globe',
label: 'Publish',
- repositoryAlias,
- api: UmbPublishDocumentEntityAction,
- },
- },
- {
- type: 'entityAction',
- alias: 'Umb.EntityAction.Document.Publish',
- name: 'Publish Document Entity Action',
- meta: {
- entityType,
- icon: 'umb:globe',
- label: 'Publish',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbPublishDocumentEntityAction,
},
},
@@ -165,7 +153,7 @@ const entityActions: Array = [
entityType,
icon: 'umb:globe',
label: 'Unpublish',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbUnpublishDocumentEntityAction,
},
},
@@ -177,10 +165,19 @@ const entityActions: Array = [
entityType,
icon: 'umb:undo',
label: 'Rollback',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbRollbackDocumentEntityAction,
},
},
];
-export const manifests = [...entityActions];
+const modals: Array = [
+ {
+ type: 'modal',
+ alias: 'Umb.Modal.CreateDocument',
+ name: 'Create Document Modal',
+ loader: () => import('./create/create-document-modal.element'),
+ },
+];
+
+export const manifests = [...entityActions, ...modals];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-bulk-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-bulk-actions/manifests.ts
index 10da47405a..7edd0952e3 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-bulk-actions/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/entity-bulk-actions/manifests.ts
@@ -1,9 +1,9 @@
+import { DOCUMENT_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbDocumentMoveEntityBulkAction } from './move/move.action';
import { UmbDocumentCopyEntityBulkAction } from './copy/copy.action';
import { ManifestEntityBulkAction } from '@umbraco-cms/extensions-registry';
const entityType = 'document';
-const repositoryAlias = 'Umb.Repository.Documents';
const entityActions: Array = [
{
@@ -14,7 +14,7 @@ const entityActions: Array = [
meta: {
entityType,
label: 'Move',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbDocumentMoveEntityBulkAction,
},
},
@@ -26,7 +26,7 @@ const entityActions: Array = [
meta: {
entityType,
label: 'Copy',
- repositoryAlias,
+ repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbDocumentCopyEntityBulkAction,
},
},
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/manifests.ts
index b36ebb6e5b..db1abd9914 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/manifests.ts
@@ -5,6 +5,7 @@ import { manifests as treeManifests } from './tree/manifests';
import { manifests as workspaceManifests } from './workspace/manifests';
import { manifests as entityActionManifests } from './entity-actions/manifests';
import { manifests as entityBulkActionManifests } from './entity-bulk-actions/manifests';
+import { manifests as modalManifests } from './modals/manifests';
export const manifests = [
...collectionManifests,
@@ -14,4 +15,5 @@ export const manifests = [
...workspaceManifests,
...entityActionManifests,
...entityBulkActionManifests,
+ ...modalManifests,
];
diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/modals/document-picker/document-picker-modal.element.ts
similarity index 76%
rename from src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts
rename to src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/modals/document-picker/document-picker-modal.element.ts
index cc00f957aa..811c812a64 100644
--- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/modals/document-picker/document-picker-modal.element.ts
@@ -1,18 +1,16 @@
import { css, html } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, state } from 'lit/decorators.js';
-import { UmbModalLayoutElement } from '../modal-layout.element';
-
-export interface UmbModalContentPickerData {
- multiple?: boolean;
- selection?: Array;
-}
-
-import { UmbTreeElement } from '../../../../backoffice/shared/components/tree/tree.element';
+import type { UmbTreeElement } from '../../../../shared/components/tree/tree.element';
+import { UmbDocumentPickerModalData, UmbDocumentPickerModalResult } from '.';
+import { UmbModalBaseElement } from '@umbraco-cms/modal';
// TODO: make use of UmbPickerLayoutBase
-@customElement('umb-modal-layout-content-picker')
-export class UmbModalLayoutContentPickerElement extends UmbModalLayoutElement {
+@customElement('umb-document-picker-modal')
+export class UmbDocumentPickerModalElement extends UmbModalBaseElement<
+ UmbDocumentPickerModalData,
+ UmbDocumentPickerModalResult
+> {
static styles = [
UUITextStyles,
css`
@@ -68,11 +66,11 @@ export class UmbModalLayoutContentPickerElement extends UmbModalLayoutElement = () => html`
+
+
+`;
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/modals/document-picker/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/modals/document-picker/index.ts
new file mode 100644
index 0000000000..b67bffe04e
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/modals/document-picker/index.ts
@@ -0,0 +1,18 @@
+import { UmbModalToken } from '@umbraco-cms/modal';
+
+export interface UmbDocumentPickerModalData {
+ multiple?: boolean;
+ selection?: Array;
+}
+
+export interface UmbDocumentPickerModalResult {
+ selection: Array;
+}
+
+export const UMB_DOCUMENT_PICKER_MODAL_TOKEN = new UmbModalToken<
+ UmbDocumentPickerModalData,
+ UmbDocumentPickerModalResult
+>('Umb.Modal.DocumentPicker', {
+ type: 'sidebar',
+ size: 'small',
+});
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/modals/manifests.ts
new file mode 100644
index 0000000000..dcaf83f082
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/modals/manifests.ts
@@ -0,0 +1,12 @@
+import type { ManifestModal } from '@umbraco-cms/extensions-registry';
+
+const modals: Array = [
+ {
+ type: 'modal',
+ alias: 'Umb.Modal.DocumentPicker',
+ name: 'Document Picker Modal',
+ loader: () => import('./document-picker/document-picker-modal.element'),
+ },
+];
+
+export const manifests = [...modals];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts
index 42b9f407c9..104b9092df 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts
@@ -1,7 +1,7 @@
import type { RepositoryTreeDataSource } from '../../../../../libs/repository/repository-tree-data-source.interface';
import { DocumentTreeServerDataSource } from './sources/document.tree.server.data';
import { UmbDocumentTreeStore, UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN } from './document.tree.store';
-import { UmbDocumentStore, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN } from './document.store';
+import { UmbDocumentStore, UMB_DOCUMENT_STORE_CONTEXT_TOKEN } from './document.store';
import { UmbDocumentServerDataSource } from './sources/document.server.data';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbContextConsumerController } from '@umbraco-cms/context-api';
@@ -25,7 +25,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
#treeStore?: UmbDocumentTreeStore;
#detailDataSource: UmbDocumentServerDataSource;
- #detailStore?: UmbDocumentStore;
+ #store?: UmbDocumentStore;
#notificationContext?: UmbNotificationContext;
@@ -41,8 +41,8 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
this.#treeStore = instance;
}),
- new UmbContextConsumerController(this.#host, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN, (instance) => {
- this.#detailStore = instance;
+ new UmbContextConsumerController(this.#host, UMB_DOCUMENT_STORE_CONTEXT_TOKEN, (instance) => {
+ this.#store = instance;
}),
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
@@ -135,7 +135,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
const { data, error } = await this.#detailDataSource.get(key);
if (data) {
- this.#detailStore?.append(data);
+ this.#store?.append(data);
}
return { data, error };
@@ -159,7 +159,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server
- this.#detailStore?.append(item);
+ this.#store?.append(item);
// TODO: Update tree store with the new item? or ask tree to request the new item?
return { error };
@@ -182,7 +182,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server
// Consider notify a workspace if a document is updated in the store while someone is editing it.
- this.#detailStore?.append(item);
+ this.#store?.append(item);
//this.#treeStore?.updateItem(item.key, { name: item.name });// Port data to tree store.
// TODO: would be nice to align the stores on methods/methodNames.
@@ -208,7 +208,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server.
// Consider notify a workspace if a document is deleted from the store while someone is editing it.
- this.#detailStore?.remove([key]);
+ this.#store?.remove([key]);
this.#treeStore?.removeItem(key);
// TODO: would be nice to align the stores on methods/methodNames.
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.store.ts
index 9f7df54d24..0dfd392794 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.store.ts
@@ -4,8 +4,6 @@ import { ArrayState } from '@umbraco-cms/observable-api';
import { UmbStoreBase } from '@umbraco-cms/store';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
-export const UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentStore');
-
/**
* @export
* @class UmbDocumentDetailStore
@@ -21,7 +19,7 @@ export class UmbDocumentStore extends UmbStoreBase {
* @memberof UmbDocumentDetailStore
*/
constructor(host: UmbControllerHostInterface) {
- super(host, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN.toString());
+ super(host, UMB_DOCUMENT_STORE_CONTEXT_TOKEN.toString());
}
/**
@@ -51,3 +49,5 @@ export class UmbDocumentStore extends UmbStoreBase {
this.#data.remove(uniques);
}
}
+
+export const UMB_DOCUMENT_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentStore');
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/manifests.ts
index 9df7f79d54..da7a9197b2 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/manifests.ts
@@ -1,7 +1,10 @@
import { UmbDocumentRepository } from '../repository/document.repository';
+import { UmbDocumentStore } from './document.store';
+import { UmbDocumentTreeStore } from './document.tree.store';
import { ManifestRepository } from 'libs/extensions-registry/repository.models';
+import { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
-export const DOCUMENT_REPOSITORY_ALIAS = 'Umb.Repository.Documents';
+export const DOCUMENT_REPOSITORY_ALIAS = 'Umb.Repository.Document';
const repository: ManifestRepository = {
type: 'repository',
@@ -10,4 +13,21 @@ const repository: ManifestRepository = {
class: UmbDocumentRepository,
};
-export const manifests = [repository];
+export const DOCUMENT_STORE_ALIAS = 'Umb.Store.Document';
+export const DOCUMENT_TREE_STORE_ALIAS = 'Umb.Store.DocumentTree';
+
+const store: ManifestStore = {
+ type: 'store',
+ alias: DOCUMENT_STORE_ALIAS,
+ name: 'Document Store',
+ class: UmbDocumentStore,
+};
+
+const treeStore: ManifestTreeStore = {
+ type: 'treeStore',
+ alias: DOCUMENT_TREE_STORE_ALIAS,
+ name: 'Document Tree Store',
+ class: UmbDocumentTreeStore,
+};
+
+export const manifests = [repository, store, treeStore];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace-split-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace-split-view.element.ts
new file mode 100644
index 0000000000..ec66755894
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace-split-view.element.ts
@@ -0,0 +1,92 @@
+import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
+import { css, html, nothing } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+import { repeat } from 'lit/directives/repeat.js';
+import { ActiveVariant } from '../../../shared/components/workspace/workspace-context/workspace-split-view-manager.class';
+import { UmbDocumentWorkspaceContext } from './document-workspace.context';
+import { UmbLitElement } from '@umbraco-cms/element';
+import '../../../shared/components/workspace/workspace-variant/workspace-variant.element';
+
+@customElement('umb-document-workspace-split-view')
+export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ :host {
+ width: 100%;
+ height: 100%;
+
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ }
+
+ #splitViews {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ }
+
+ #breadcrumbs {
+ margin: 0 var(--uui-size-layout-1);
+ }
+ `,
+ ];
+
+ private _workspaceContext?: UmbDocumentWorkspaceContext;
+
+ @state()
+ _unique?: string;
+
+ @state()
+ _variants?: Array;
+
+ constructor() {
+ super();
+
+ this.consumeContext('umbWorkspaceContext', (context) => {
+ this._workspaceContext = context;
+ this._observeActiveVariantInfo();
+ });
+ }
+
+ private _observeActiveVariantInfo() {
+ if (!this._workspaceContext) return;
+ this.observe(
+ this._workspaceContext.splitView.activeVariantsInfo,
+ (variants) => {
+ this._variants = variants;
+ },
+ '_observeActiveVariantsInfo'
+ );
+ }
+
+ render() {
+ return this._variants
+ ? html`
+ ${repeat(
+ this._variants,
+ (view) => view.index,
+ (view) => html`
+
+ `
+ )}
+
+
+
+ Breadcrumbs
+ `
+ : nothing;
+ }
+}
+
+export default UmbDocumentWorkspaceSplitViewElement;
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-document-workspace-split-view': UmbDocumentWorkspaceSplitViewElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts
index fe520cf1d6..f62c5943fc 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts
@@ -4,17 +4,12 @@ import { UmbDocumentTypeRepository } from '../../document-types/repository/docum
import { UmbWorkspaceVariableEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-variable-entity-context.interface';
import { UmbVariantId } from '../../../shared/variants/variant-id.class';
import { UmbWorkspacePropertyStructureManager } from '../../../shared/components/workspace/workspace-context/workspace-property-structure-manager.class';
+import { UmbWorkspaceSplitViewManager } from '../../../shared/components/workspace/workspace-context/workspace-split-view-manager.class';
import type { DocumentModel } from '@umbraco-cms/backend-api';
-import { partialUpdateFrozenArray, ObjectState, ArrayState, UmbObserverController } from '@umbraco-cms/observable-api';
+import { partialUpdateFrozenArray, ObjectState, UmbObserverController } from '@umbraco-cms/observable-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
// TODO: should this context be called DocumentDraft instead of workspace? or should the draft be part of this?
-
-export type ActiveVariant = {
- index: number;
- culture: string | null;
- segment: string | null;
-};
// TODO: Should we have a DocumentStructureContext and maybe even a DocumentDraftContext?
type EntityType = DocumentModel;
@@ -33,21 +28,21 @@ export class UmbDocumentWorkspaceContext
* The document is the current state/draft version of the document.
*/
#draft = new ObjectState(undefined);
- documentTypeKey = this.#draft.getObservablePart((data) => data?.contentTypeKey);
+ readonly unique = this.#draft.getObservablePart((data) => data?.key);
+ readonly documentTypeKey = this.#draft.getObservablePart((data) => data?.contentTypeKey);
- variants = this.#draft.getObservablePart((data) => data?.variants || []);
- urls = this.#draft.getObservablePart((data) => data?.urls || []);
- templateKey = this.#draft.getObservablePart((data) => data?.templateKey || null);
-
- #activeVariantsInfo = new ArrayState([], (x) => x.index);
- activeVariantsInfo = this.#activeVariantsInfo.asObservable();
+ readonly variants = this.#draft.getObservablePart((data) => data?.variants || []);
+ readonly urls = this.#draft.getObservablePart((data) => data?.urls || []);
+ readonly templateKey = this.#draft.getObservablePart((data) => data?.templateKey || null);
readonly structure;
+ readonly splitView;
constructor(host: UmbControllerHostInterface) {
super(host, new UmbDocumentRepository(host));
this.structure = new UmbWorkspacePropertyStructureManager(this.host, new UmbDocumentTypeRepository(this.host));
+ this.splitView = new UmbWorkspaceSplitViewManager(this.host);
new UmbObserverController(this.host, this.documentTypeKey, (key) => this.structure.loadType(key));
}
@@ -90,28 +85,10 @@ export class UmbDocumentWorkspaceContext
return 'document';
}
- setActiveVariant(index: number, culture: string | null, segment: string | null) {
- const activeVariants = [...this.#activeVariantsInfo.getValue()];
- if (index < activeVariants.length) {
- activeVariants[index] = { index, culture, segment };
- } else {
- activeVariants.push({ index, culture, segment });
- }
- this.#activeVariantsInfo.next(activeVariants);
- }
-
- openSplitView(culture: string | null, segment: string | null) {
- this.setActiveVariant(1, culture, segment);
- }
-
getVariant(variantId: UmbVariantId) {
return this.#draft.getValue()?.variants?.find((x) => variantId.compare(x));
}
- activeVariantInfoByIndex(index: number) {
- return this.#activeVariantsInfo.getObservablePart((data) => data[index] || undefined);
- }
-
getName(variantId?: UmbVariantId) {
const variants = this.#draft.getValue()?.variants;
if (!variants) return;
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.element.ts
index e4fb29c8ad..d2f69c3f86 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.element.ts
@@ -1,12 +1,16 @@
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
-import { css, html, nothing } from 'lit';
+import { css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
-import { repeat } from 'lit/directives/repeat.js';
+import { IRoute, IRoutingInfo } from 'router-slot';
import type { UmbWorkspaceEntityElement } from '../../../shared/components/workspace/workspace-entity-element.interface';
-import { ActiveVariant, UmbDocumentWorkspaceContext } from './document-workspace.context';
+import { UmbVariantId } from '../../../shared/variants/variant-id.class';
+import { ActiveVariant } from '../../../shared/components/workspace/workspace-context/workspace-split-view-manager.class';
+import { UmbDocumentWorkspaceContext } from './document-workspace.context';
+import { UmbDocumentWorkspaceSplitViewElement } from './document-workspace-split-view.element';
import { UmbLitElement } from '@umbraco-cms/element';
import '../../../shared/components/workspace/workspace-variant/workspace-variant.element';
-import { DocumentModel } from '@umbraco-cms/backend-api';
+import { DocumentModel, VariantViewModelBaseModel } from '@umbraco-cms/backend-api';
+import { UmbRouterSlotInitEvent } from '@umbraco-cms/router';
@customElement('umb-document-workspace')
export class UmbDocumentWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement {
@@ -14,7 +18,7 @@ export class UmbDocumentWorkspaceElement extends UmbLitElement implements UmbWor
UUITextStyles,
css`
:host {
- display: flex;
+ display: block;
width: 100%;
height: 100%;
}
@@ -22,16 +26,29 @@ export class UmbDocumentWorkspaceElement extends UmbLitElement implements UmbWor
];
private _workspaceContext: UmbDocumentWorkspaceContext = new UmbDocumentWorkspaceContext(this);
+ //private _defaultVariant?: VariantViewModelBaseModel;
+ private splitViewElement = new UmbDocumentWorkspaceSplitViewElement();
@state()
_unique?: string;
+ @state()
+ _routes?: Array;
+
+ @state()
+ _availableVariants: Array = [];
+
@state()
_workspaceSplitViews: Array = [];
constructor() {
super();
- this.observe(this._workspaceContext.activeVariantsInfo, (variants) => {
+
+ this.observe(this._workspaceContext.variants, (variants) => {
+ this._availableVariants = variants;
+ this._generateRoutes();
+ });
+ this.observe(this._workspaceContext.splitView.activeVariantsInfo, (variants) => {
this._workspaceSplitViews = variants;
});
}
@@ -48,28 +65,80 @@ export class UmbDocumentWorkspaceElement extends UmbLitElement implements UmbWor
private _gotDocumentData(data: DocumentModel | undefined) {
if (data && data.variants && data.variants.length > 0) {
- this._workspaceContext.setActiveVariant(0, data.variants[0].culture || null, data.variants[0].segment || null);
+ //this._defaultVariant = data.variants[0];
this._unique = data.key;
+ // Maybe we need to re-generate routes here?
} else {
// Fail beautifully?
}
}
+ private _handleVariantFolderPart(index: number, folderPart: string) {
+ const variantSplit = folderPart.split('_');
+ const culture = variantSplit[0];
+ const segment = variantSplit[1];
+ this._workspaceContext.splitView.setActiveVariant(index, culture, segment);
+ }
+
+ private _generateRoutes() {
+ if (!this._availableVariants || this._availableVariants.length === 0) return;
+
+ // Generate split view routes for all available routes
+ const routes: Array = [];
+
+ // Split view routes:
+ this._availableVariants.forEach((variantA) => {
+ this._availableVariants.forEach((variantB) => {
+ routes.push({
+ path: new UmbVariantId(variantA).toString() + '_&_' + new UmbVariantId(variantB).toString(),
+ //component: () => import('./document-workspace-split-view.element'),
+ component: this.splitViewElement,
+ setup: (component: HTMLElement | Promise, info: IRoutingInfo) => {
+ // Set split view/active info..
+ const variantSplit = info.match.fragments.consumed.split('_&_');
+ variantSplit.forEach((part, index) => {
+ this._handleVariantFolderPart(index, part);
+ });
+ },
+ });
+ });
+ });
+
+ // Single view:
+ this._availableVariants.forEach((variant) => {
+ routes.push({
+ path: new UmbVariantId(variant).toString(),
+ //component: () => import('./document-workspace-split-view.element'),
+ component: this.splitViewElement,
+ setup: (component: HTMLElement | Promise, info: IRoutingInfo) => {
+ // cause we might come from a split-view, we need to reset index 1.
+ this._workspaceContext.splitView.removeActiveVariant(1);
+ this._handleVariantFolderPart(0, info.match.fragments.consumed);
+ },
+ });
+ });
+
+ if (routes.length !== 0) {
+ // Using first single view as the default route for now (hence the math below):
+ routes.push({
+ path: '**',
+ redirectTo: routes[this._availableVariants.length * this._availableVariants.length]?.path,
+ });
+ }
+
+ this._routes = routes;
+ }
+
+ private _gotWorkspaceRoute = (e: UmbRouterSlotInitEvent) => {
+ this._workspaceContext.splitView.setWorkspaceRoute(e.target.absoluteRouterPath);
+ };
+
render() {
- return this._unique
- ? repeat(
- this._workspaceSplitViews,
- (view) => view.index,
- (view) => html`
-
-
-
- `
- )
- : nothing;
+ return this._routes
+ ? html`${this.splitViewElement}`
+ : '';
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts
index a7ac1b3fd3..26b33df552 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts
@@ -89,16 +89,6 @@ export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement {
private _createRoutes() {
const routes: any[] = [];
- if (this._hasRootGroups) {
- routes.push({
- path: 'root',
- component: () => import('./document-workspace-view-edit-tab.element'),
- setup: (component: Promise) => {
- (component as any).noTabName = true;
- },
- });
- }
-
if (this._tabs.length > 0) {
this._tabs?.forEach((tab) => {
const tabName = tab.name;
@@ -112,12 +102,17 @@ export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement {
});
}
- if (routes.length !== 0) {
+ if (this._hasRootGroups) {
routes.push({
path: '',
- redirectTo: routes[0]?.path,
+ component: () => import('./document-workspace-view-edit-tab.element'),
+ setup: (component: Promise) => {
+ (component as any).noTabName = true;
+ },
});
+ }
+ if (routes.length !== 0) {
routes.push({
path: '**',
redirectTo: routes[0]?.path,
@@ -129,14 +124,14 @@ export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement {
render() {
return html`
- ${this._tabs.length > 1
+ ${this._routerPath && this._tabs.length > 1
? html`
${this._hasRootGroups && this._tabs.length > 1
? html`
Content
`
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/index.ts
index 5f5aac6a97..7cfabc7cce 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/index.ts
@@ -8,18 +8,17 @@ import { manifests as documentManifests } from './documents/manifests';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTypes } from '@umbraco-cms/extensions-registry';
-const registerExtensions = (manifests: Array) => {
- manifests.forEach((manifest) => {
- if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
- umbExtensionsRegistry.register(manifest);
- });
-};
-
-registerExtensions([
+export const manifests = [
...dashboardManifests,
...contentSectionManifests,
...contentMenuManifest,
...documentBlueprintManifests,
...documentTypeManifests,
...documentManifests,
-]);
+];
+
+const registerExtensions = (manifests: Array) => {
+ manifests.forEach((manifest) => umbExtensionsRegistry.register(manifest));
+};
+
+registerExtensions(manifests);
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/index.ts
index 7afe8c49db..76a1e6c199 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/index.ts
@@ -6,11 +6,10 @@ import { manifests as mediaTypesManifests } from './media-types/manifests';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTypes } from '@umbraco-cms/extensions-registry';
+export const manifests = [...mediaSectionManifests, ...mediaMenuManifests, ...mediaManifests, ...mediaTypesManifests];
+
const registerExtensions = (manifests: Array) => {
- manifests.forEach((manifest) => {
- if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
- umbExtensionsRegistry.register(manifest);
- });
+ manifests.forEach((manifest) => umbExtensionsRegistry.register(manifest));
};
-registerExtensions([...mediaSectionManifests, ...mediaMenuManifests, ...mediaManifests, ...mediaTypesManifests]);
+registerExtensions(manifests);
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/manifests.ts
index 2460e11e38..dc5de79647 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/manifests.ts
@@ -1,13 +1,33 @@
import { UmbMediaTypeRepository } from './media-type.repository';
+import { UmbMediaTypeStore } from './media-type.detail.store';
+import { UmbMediaTypeTreeStore } from './media-type.tree.store';
import { ManifestRepository } from 'libs/extensions-registry/repository.models';
+import { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
-export const MEDIA_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.MediaTypes';
+export const MEDIA_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.MediaType';
const repository: ManifestRepository = {
type: 'repository',
alias: MEDIA_TYPE_REPOSITORY_ALIAS,
- name: 'Media Types Repository',
+ name: 'Media Type Repository',
class: UmbMediaTypeRepository,
};
-export const manifests = [repository];
+export const MEDIA_TYPE_STORE_ALIAS = 'Umb.Store.MediaType';
+export const MEDIA_TYPE_TREE_STORE_ALIAS = 'Umb.Store.MediaTypeTree';
+
+const store: ManifestStore = {
+ type: 'store',
+ alias: MEDIA_TYPE_STORE_ALIAS,
+ name: 'Media Type Store',
+ class: UmbMediaTypeStore,
+};
+
+const treeStore: ManifestTreeStore = {
+ type: 'treeStore',
+ alias: MEDIA_TYPE_TREE_STORE_ALIAS,
+ name: 'Media Type Tree Store',
+ class: UmbMediaTypeTreeStore,
+};
+
+export const manifests = [store, treeStore, repository];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.detail.store.ts
index 841ca9822a..57dc378d61 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.detail.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.detail.store.ts
@@ -10,13 +10,11 @@ import type { MediaTypeDetails } from '@umbraco-cms/models';
* @extends {UmbStoreBase}
* @description - Details Data Store for Media Types
*/
-export class UmbMediaTypeDetailStore
- extends UmbStoreBase
-{
+export class UmbMediaTypeStore extends UmbStoreBase {
#data = new ArrayState([], (x) => x.key);
constructor(host: UmbControllerHostInterface) {
- super(host, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString());
+ super(host, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN.toString());
}
append(mediaType: MediaTypeDetails) {
@@ -28,6 +26,4 @@ export class UmbMediaTypeDetailStore
}
}
-export const UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken(
- 'UmbMediaTypeDetailStore'
-);
+export const UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMediaTypeStore');
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts
index 1ded02b0fe..8123483e57 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts
@@ -1,6 +1,6 @@
import { UmbMediaTypeTreeStore, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN } from './media-type.tree.store';
import { UmbMediaTypeDetailServerDataSource } from './sources/media-type.detail.server.data';
-import { UmbMediaTypeDetailStore, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from './media-type.detail.store';
+import { UmbMediaTypeStore, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN } from './media-type.detail.store';
import { MediaTypeTreeServerDataSource } from './sources/media-type.tree.server.data';
import { ProblemDetailsModel } from '@umbraco-cms/backend-api';
import { UmbContextConsumerController } from '@umbraco-cms/context-api';
@@ -18,7 +18,7 @@ export class UmbMediaTypeRepository implements UmbTreeRepository {
#treeStore?: UmbMediaTypeTreeStore;
#detailSource: UmbMediaTypeDetailServerDataSource;
- #detailStore?: UmbMediaTypeDetailStore;
+ #store?: UmbMediaTypeStore;
#notificationContext?: UmbNotificationContext;
@@ -30,8 +30,8 @@ export class UmbMediaTypeRepository implements UmbTreeRepository {
this.#detailSource = new UmbMediaTypeDetailServerDataSource(this.#host);
this.#init = Promise.all([
- new UmbContextConsumerController(this.#host, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN, (instance) => {
- this.#detailStore = instance;
+ new UmbContextConsumerController(this.#host, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN, (instance) => {
+ this.#store = instance;
}),
new UmbContextConsumerController(this.#host, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => {
@@ -120,7 +120,7 @@ export class UmbMediaTypeRepository implements UmbTreeRepository {
const { data, error } = await this.#detailSource.get(key);
if (data) {
- this.#detailStore?.append(data);
+ this.#store?.append(data);
}
return { data, error };
}
@@ -150,7 +150,7 @@ export class UmbMediaTypeRepository implements UmbTreeRepository {
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server
// Consider notify a workspace if a media type is updated in the store while someone is editing it.
- this.#detailStore?.append(mediaType);
+ this.#store?.append(mediaType);
this.#treeStore?.updateItem(mediaType.key, { name: mediaType.name });
// TODO: would be nice to align the stores on methods/methodNames.
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.tree.store.ts
index 0f82fd5a61..a70127f5d6 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.tree.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.tree.store.ts
@@ -9,7 +9,6 @@ import type { UmbControllerHostInterface } from '@umbraco-cms/controller';
* @description - Tree Data Store for Media Types
*/
export class UmbMediaTypeTreeStore extends UmbTreeStoreBase {
-
/**
* Creates an instance of UmbMediaTypeTreeStore.
* @param {UmbControllerHostInterface} host
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-actions/manifests.ts
index 07c8ca11f8..e34ec47335 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-actions/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-actions/manifests.ts
@@ -1,3 +1,4 @@
+import { MEDIA_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbTrashEntityAction } from '@umbraco-cms/entity-action';
import { ManifestEntityAction } from 'libs/extensions-registry/entity-action.models';
@@ -11,7 +12,7 @@ const entityActions: Array = [
icon: 'umb:trash',
label: 'Trash',
api: UmbTrashEntityAction,
- repositoryAlias: 'Umb.Repository.Media',
+ repositoryAlias: MEDIA_REPOSITORY_ALIAS,
},
},
];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/manifests.ts
index 14a585f00a..86b84b2856 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/manifests.ts
@@ -1,10 +1,10 @@
+import { MEDIA_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbMediaMoveEntityBulkAction } from './move/move.action';
import { UmbMediaCopyEntityBulkAction } from './copy/copy.action';
import { UmbMediaTrashEntityBulkAction } from './trash/trash.action';
import { ManifestEntityBulkAction } from '@umbraco-cms/extensions-registry';
const entityType = 'media';
-const repositoryAlias = 'Umb.Repository.Media';
const entityActions: Array = [
{
@@ -15,7 +15,7 @@ const entityActions: Array = [
meta: {
entityType,
label: 'Move',
- repositoryAlias,
+ repositoryAlias: MEDIA_REPOSITORY_ALIAS,
api: UmbMediaMoveEntityBulkAction,
},
},
@@ -27,7 +27,7 @@ const entityActions: Array = [
meta: {
entityType,
label: 'Copy',
- repositoryAlias,
+ repositoryAlias: MEDIA_REPOSITORY_ALIAS,
api: UmbMediaCopyEntityBulkAction,
},
},
@@ -39,7 +39,7 @@ const entityActions: Array = [
meta: {
entityType,
label: 'Trash',
- repositoryAlias,
+ repositoryAlias: MEDIA_REPOSITORY_ALIAS,
api: UmbMediaTrashEntityBulkAction,
},
},
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/move/move.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/move/move.action.ts
index 30ae1aeac3..42f84f7b43 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/move/move.action.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/move/move.action.ts
@@ -1,4 +1,5 @@
import type { UmbMediaRepository } from '../../repository/media.repository';
+import { UMB_MEDIA_PICKER_MODAL_TOKEN } from '../../modals/media-picker';
import { UmbEntityBulkActionBase } from '@umbraco-cms/entity-action';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbContextConsumerController } from '@umbraco-cms/context-api';
@@ -17,9 +18,14 @@ export class UmbMediaMoveEntityBulkAction extends UmbEntityBulkActionBase {
#modalContext?: UmbModalContext;
@@ -25,7 +26,7 @@ export class UmbMediaTrashEntityBulkAction extends UmbEntityBulkActionBase = [
+ {
+ type: 'modal',
+ alias: 'Umb.Modal.MediaPicker',
+ name: 'Media Picker Modal',
+ loader: () => import('./media-picker/media-picker-modal.element'),
+ },
+];
+
+export const manifests = [...modals];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/modals/media-picker/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/modals/media-picker/index.ts
new file mode 100644
index 0000000000..7075158e15
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/modals/media-picker/index.ts
@@ -0,0 +1,18 @@
+import { UmbModalToken } from '@umbraco-cms/modal';
+
+export interface UmbMediaPickerModalData {
+ multiple?: boolean;
+ selection: Array;
+}
+
+export interface UmbMediaPickerModalResult {
+ selection: Array;
+}
+
+export const UMB_MEDIA_PICKER_MODAL_TOKEN = new UmbModalToken(
+ 'Umb.Modal.MediaPicker',
+ {
+ type: 'sidebar',
+ size: 'small',
+ }
+);
diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/media-picker/modal-layout-media-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/modals/media-picker/media-picker-modal.element.ts
similarity index 77%
rename from src/Umbraco.Web.UI.Client/src/core/modal/layouts/media-picker/modal-layout-media-picker.element.ts
rename to src/Umbraco.Web.UI.Client/src/backoffice/media/media/modals/media-picker/media-picker-modal.element.ts
index 15f5d39ada..82fa6411f0 100644
--- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/media-picker/modal-layout-media-picker.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/modals/media-picker/media-picker-modal.element.ts
@@ -1,17 +1,15 @@
import { css, html } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, state } from 'lit/decorators.js';
-import { UmbModalLayoutElement } from '../modal-layout.element';
+import { UmbTreeElement } from '../../../../shared/components/tree/tree.element';
+import { UmbMediaPickerModalData, UmbMediaPickerModalResult } from '.';
+import { UmbModalBaseElement } from '@umbraco-cms/modal';
-export interface UmbModalMediaPickerData {
- multiple?: boolean;
- selection: Array;
-}
-
-import { UmbTreeElement } from '../../../../backoffice/shared/components/tree/tree.element';
-
-@customElement('umb-modal-layout-media-picker')
-export class UmbModalLayoutMediaPickerElement extends UmbModalLayoutElement {
+@customElement('umb-media-picker-modal')
+export class UmbMediaPickerModalElement extends UmbModalBaseElement<
+ UmbMediaPickerModalData,
+ UmbMediaPickerModalResult
+> {
static styles = [
UUITextStyles,
css`
@@ -67,11 +65,11 @@ export class UmbModalLayoutMediaPickerElement extends UmbModalLayoutElement {
- this.#detailStore = instance;
+ new UmbContextConsumerController(this.#host, UMB_MEDIA_STORE_CONTEXT_TOKEN, (instance) => {
+ this.#store = instance;
}),
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
@@ -133,7 +133,7 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor
const { data, error } = await this.#detailDataSource.get(key);
if (data) {
- this.#detailStore?.append(data);
+ this.#store?.append(data);
}
return { data, error };
@@ -157,7 +157,7 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server
- this.#detailStore?.append(template);
+ this.#store?.append(template);
// TODO: Update tree store with the new item? or ask tree to request the new item?
return { error };
@@ -180,7 +180,7 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server
// Consider notify a workspace if a template is updated in the store while someone is editing it.
- this.#detailStore?.append(document);
+ this.#store?.append(document);
this.#treeStore?.updateItem(document.key, { name: document.name });
// TODO: would be nice to align the stores on methods/methodNames.
@@ -206,7 +206,7 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server.
// Consider notify a workspace if a template is deleted from the store while someone is editing it.
- this.#detailStore?.remove([key]);
+ this.#store?.remove([key]);
this.#treeStore?.removeItem(key);
// TODO: would be nice to align the stores on methods/methodNames.
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.store.ts
similarity index 67%
rename from src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.detail.store.ts
rename to src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.store.ts
index 9130ba65bf..070a596885 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.detail.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.store.ts
@@ -4,30 +4,28 @@ import { ArrayState } from '@umbraco-cms/observable-api';
import { UmbStoreBase } from '@umbraco-cms/store';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
-export const UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMediaDetailStore');
-
/**
* @export
- * @class UmbMediaDetailStore
+ * @class UmbMediaStore
* @extends {UmbStoreBase}
* @description - Data Store for Template Details
*/
-export class UmbMediaDetailStore extends UmbStoreBase {
+export class UmbMediaStore extends UmbStoreBase {
#data = new ArrayState([], (x) => x.key);
/**
- * Creates an instance of UmbMediaDetailStore.
+ * Creates an instance of UmbMediaStore.
* @param {UmbControllerHostInterface} host
- * @memberof UmbMediaDetailStore
+ * @memberof UmbMediaStore
*/
constructor(host: UmbControllerHostInterface) {
- super(host, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN.toString());
+ super(host, UMB_MEDIA_STORE_CONTEXT_TOKEN.toString());
}
/**
* Append a media to the store
* @param {MediaDetails} media
- * @memberof UmbMediaDetailStore
+ * @memberof UmbMediaStore
*/
append(media: MediaDetails) {
this.#data.append([media]);
@@ -36,9 +34,11 @@ export class UmbMediaDetailStore extends UmbStoreBase {
/**
* Removes media in the store with the given uniques
* @param {string[]} uniques
- * @memberof UmbMediaDetailStore
+ * @memberof UmbMediaStore
*/
remove(uniques: string[]) {
this.#data.remove(uniques);
}
}
+
+export const UMB_MEDIA_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMediaStore');
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.tree.store.ts
index 07299c5e8f..a070665602 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.tree.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.tree.store.ts
@@ -1,7 +1,7 @@
import { EntityTreeItemModel } from '@umbraco-cms/backend-api';
import { UmbContextToken } from '@umbraco-cms/context-api';
import { ArrayState } from '@umbraco-cms/observable-api';
-import { UmbStoreBase } from '@umbraco-cms/store';
+import { UmbTreeStoreBase } from '@umbraco-cms/store';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
export const UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMediaTreeStore');
@@ -9,11 +9,10 @@ export const UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken([], (x) => x.key);
/**
@@ -24,68 +23,4 @@ export class UmbMediaTreeStore extends UmbStoreBase {
constructor(host: UmbControllerHostInterface) {
super(host, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN.toString());
}
-
- /**
- * Appends items to the store
- * @param {Array} items
- * @memberof UmbMediaTreeStore
- */
- appendItems(items: Array) {
- this.#data.append(items);
- }
-
- /**
- * Updates an item in the store
- * @param {string} key
- * @param {Partial} data
- * @memberof UmbMediaTreeStore
- */
- updateItem(key: string, data: Partial) {
- const entries = this.#data.getValue();
- const entry = entries.find((entry) => entry.key === key);
-
- if (entry) {
- this.#data.appendOne({ ...entry, ...data });
- }
- }
-
- /**
- * Removes an item from the store
- * @param {string} key
- * @memberof UmbMediaTreeStore
- */
- removeItem(key: string) {
- const entries = this.#data.getValue();
- const entry = entries.find((entry) => entry.key === key);
-
- if (entry) {
- this.#data.remove([key]);
- }
- }
-
- /**
- * An observable to observe the root items
- * @memberof UmbMediaTreeStore
- */
- rootItems = this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null));
-
- /**
- * Returns an observable to observe the children of a given parent
- * @param {(string | null)} parentKey
- * @return {*}
- * @memberof UmbMediaTreeStore
- */
- childrenOf(parentKey: string | null) {
- return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === parentKey));
- }
-
- /**
- * Returns an observable to observe the items with the given keys
- * @param {Array} keys
- * @return {*}
- * @memberof UmbMediaTreeStore
- */
- items(keys: Array) {
- return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? '')));
- }
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/manifests.ts
index 29af2a95c6..3c363283c7 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/manifests.ts
@@ -1,3 +1,4 @@
+import { MEDIA_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbSaveWorkspaceAction } from '@umbraco-cms/workspace';
import type {
ManifestWorkspace,
@@ -59,7 +60,7 @@ const workspaceViewCollections: Array = [
pathname: 'collection',
icon: 'umb:grid',
entityType: 'media',
- repositoryAlias: 'Umb.Repository.Media',
+ repositoryAlias: MEDIA_REPOSITORY_ALIAS,
},
},
];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/section.manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/section.manifests.ts
index e7f137f2b8..9ba71485a5 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/media/section.manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/section.manifests.ts
@@ -1,3 +1,4 @@
+import { MEDIA_REPOSITORY_ALIAS } from './media/repository/manifests';
import type { ManifestDashboardCollection, ManifestSection, ManifestMenuSectionSidebarApp } from '@umbraco-cms/models';
const sectionAlias = 'Umb.Section.Media';
@@ -24,7 +25,7 @@ const dashboards: Array = [
sections: [sectionAlias],
pathname: 'media-management',
entityType: 'media',
- repositoryAlias: 'Umb.Repository.Media',
+ repositoryAlias: MEDIA_REPOSITORY_ALIAS,
},
},
];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/index.ts
index 0f5f91f7e7..386a38582f 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/index.ts
@@ -7,17 +7,16 @@ import { manifests as memberManifests } from './members/manifests';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTypes } from '@umbraco-cms/extensions-registry';
-const registerExtensions = (manifests: Array) => {
- manifests.forEach((manifest) => {
- if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
- umbExtensionsRegistry.register(manifest);
- });
-};
-
-registerExtensions([
+export const manifests = [
...memberSectionManifests,
...menuSectionManifests,
...memberGroupManifests,
...memberTypeManifests,
...memberManifests,
-]);
+];
+
+const registerExtensions = (manifests: Array) => {
+ manifests.forEach((manifest) => umbExtensionsRegistry.register(manifest));
+};
+
+registerExtensions(manifests);
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/entity-actions/manifests.ts
index 5e84d8d6db..e132cc6f8f 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/entity-actions/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/entity-actions/manifests.ts
@@ -1,3 +1,4 @@
+import { MEMBER_GROUP_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbDeleteEntityAction } from '@umbraco-cms/entity-action';
import { ManifestEntityAction } from 'libs/extensions-registry/entity-action.models';
@@ -11,7 +12,7 @@ const entityActions: Array = [
icon: 'umb:trash',
label: 'Delete',
api: UmbDeleteEntityAction,
- repositoryAlias: 'Umb.Repository.MemberGroup',
+ repositoryAlias: MEMBER_GROUP_REPOSITORY_ALIAS,
},
},
];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/manifests.ts
index 728b8ae95f..c4fb973526 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/manifests.ts
@@ -1,5 +1,8 @@
import { UmbMemberGroupRepository } from './member-group.repository';
+import { UmbMemberGroupStore } from './member-group.store';
+import { UmbMemberGroupTreeStore } from './member-group.tree.store';
import { ManifestRepository } from 'libs/extensions-registry/repository.models';
+import { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
export const MEMBER_GROUP_REPOSITORY_ALIAS = 'Umb.Repository.MemberGroup';
@@ -10,4 +13,21 @@ const repository: ManifestRepository = {
class: UmbMemberGroupRepository,
};
-export const manifests = [repository];
+export const MEMBER_GROUP_STORE_ALIAS = 'Umb.Store.MemberGroup';
+export const MEMBER_GROUP_TREE_STORE_ALIAS = 'Umb.Store.MemberGroupTree';
+
+const store: ManifestStore = {
+ type: 'store',
+ alias: MEMBER_GROUP_STORE_ALIAS,
+ name: 'Member Group Store',
+ class: UmbMemberGroupStore,
+};
+
+const treeStore: ManifestTreeStore = {
+ type: 'treeStore',
+ alias: MEMBER_GROUP_TREE_STORE_ALIAS,
+ name: 'Member Group Tree Store',
+ class: UmbMemberGroupTreeStore,
+};
+
+export const manifests = [store, treeStore, repository];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts
index 80e323c25e..0c0f315b71 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts
@@ -1,6 +1,6 @@
import { UmbMemberGroupTreeStore, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN } from './member-group.tree.store';
import { UmbMemberGroupDetailServerDataSource } from './sources/member-group.detail.server.data';
-import { UmbMemberGroupDetailStore, UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN } from './member-group.detail.store';
+import { UmbMemberGroupStore, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN } from './member-group.store';
import { MemberGroupTreeServerDataSource } from './sources/member-group.tree.server.data';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/notification';
@@ -19,7 +19,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
#treeStore?: UmbMemberGroupTreeStore;
#detailSource: UmbMemberGroupDetailServerDataSource;
- #detailStore?: UmbMemberGroupDetailStore;
+ #store?: UmbMemberGroupStore;
#notificationContext?: UmbNotificationContext;
@@ -33,8 +33,8 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
this.#treeStore = instance;
});
- new UmbContextConsumerController(this.#host, UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN, (instance) => {
- this.#detailStore = instance;
+ new UmbContextConsumerController(this.#host, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN, (instance) => {
+ this.#store = instance;
});
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
@@ -74,7 +74,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
async rootTreeItems() {
await this.#init;
- return this.#treeStore!.rootItems();
+ return this.#treeStore!.rootItems;
}
async treeItemsOf(parentKey: string | null) {
@@ -106,7 +106,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
const { data, error } = await this.#detailSource.get(key);
if (data) {
- this.#detailStore?.append(data);
+ this.#store?.append(data);
}
return { data, error };
}
@@ -144,7 +144,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
this.#notificationContext?.peek('positive', notification);
}
- this.#detailStore?.append(memberGroup);
+ this.#store?.append(memberGroup);
this.#treeStore?.updateItem(memberGroup.key, { name: memberGroup.name });
return { error };
@@ -168,7 +168,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server.
// Consider notify a workspace if a template is deleted from the store while someone is editing it.
- this.#detailStore?.remove([key]);
+ this.#store?.remove([key]);
this.#treeStore?.removeItem(key);
// TODO: would be nice to align the stores on methods/methodNames.
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.store.ts
similarity index 62%
rename from src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.detail.store.ts
rename to src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.store.ts
index ce6053cbd6..864430facf 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.detail.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.store.ts
@@ -4,23 +4,17 @@ import { ArrayState } from '@umbraco-cms/observable-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbStoreBase } from '@umbraco-cms/store';
-export const UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken(
- 'UmbMemberGroupDetailStore'
-);
-
/**
* @export
- * @class UmbMemberGroupDetailStore
+ * @class UmbMemberGroupStore
* @extends {UmbStoreBase}
- * @description - Details Data Store for Member Groups
+ * @description - Data Store for Member Groups
*/
-export class UmbMemberGroupDetailStore
- extends UmbStoreBase
-{
+export class UmbMemberGroupStore extends UmbStoreBase {
#data = new ArrayState([], (x) => x.key);
constructor(host: UmbControllerHostInterface) {
- super(host, UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN.toString());
+ super(host, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN.toString());
}
append(memberGroup: MemberGroupDetails) {
@@ -31,3 +25,5 @@ export class UmbMemberGroupDetailStore
this.#data.remove(uniques);
}
}
+
+export const UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMemberGroupStore');
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.tree.store.ts
index bad706d967..4d1f4ce44a 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.tree.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.tree.store.ts
@@ -1,93 +1,22 @@
-import type { EntityTreeItemModel } from '@umbraco-cms/backend-api';
import { UmbContextToken } from '@umbraco-cms/context-api';
-import { ArrayState } from '@umbraco-cms/observable-api';
-import { UmbStoreBase } from '@umbraco-cms/store';
+import { UmbTreeStoreBase } from '@umbraco-cms/store';
import type { UmbControllerHostInterface } from '@umbraco-cms/controller';
/**
* @export
* @class UmbMemberGroupTreeStore
- * @extends {UmbStoreBase}
+ * @extends {UmbTreeStoreBase}
* @description - Tree Data Store for Member Groups
*/
-export class UmbMemberGroupTreeStore extends UmbStoreBase {
- #data = new ArrayState([], (x) => x.key);
-
+export class UmbMemberGroupTreeStore extends UmbTreeStoreBase {
/**
- * Creates an instance of UmbTemplateTreeStore.
+ * Creates an instance of UmbMemberGroupTreeStore.
* @param {UmbControllerHostInterface} host
* @memberof UmbMemberGroupTreeStore
*/
constructor(host: UmbControllerHostInterface) {
super(host, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN.toString());
}
-
- /**
- * Appends items to the store
- * @param {Array} items
- * @memberof UmbTemplateTreeStore
- */
- appendItems(items: Array) {
- this.#data.append(items);
- }
-
- /**
- * Updates an item in the store
- * @param {string} key
- * @param {Partial} data
- * @memberof UmbMemberGroupTreeStore
- */
- updateItem(key: string, data: Partial) {
- const entries = this.#data.getValue();
- const entry = entries.find((entry) => entry.key === key);
-
- if (entry) {
- this.#data.appendOne({ ...entry, ...data });
- }
- }
-
- /**
- * Removes an item from the store
- * @param {string} key
- * @memberof UmbMemberGroupTreeStore
- */
- removeItem(key: string) {
- const entries = this.#data.getValue();
- const entry = entries.find((entry) => entry.key === key);
-
- if (entry) {
- this.#data.remove([key]);
- }
- }
-
- /**
- * Returns an observable to observe the root items
- * @return {*}
- * @memberof UmbMemberGroupTreeStore
- */
- rootItems() {
- return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null));
- }
-
- /**
- * Returns an observable to observe the children of a given parent
- * @param {(string | null)} parentKey
- * @return {*}
- * @memberof UmbMemberGroupTreeStore
- */
- childrenOf(parentKey: string | null) {
- return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === parentKey));
- }
-
- /**
- * Returns an observable to observe the items with the given keys
- * @param {Array} keys
- * @return {*}
- * @memberof UmbMemberGroupTreeStore
- */
- items(keys: Array) {
- return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? '')));
- }
}
export const UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken(
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/manifests.ts
index da187f5066..74c4fd94d3 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/manifests.ts
@@ -1,4 +1,3 @@
-import { MEMBER_GROUP_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbSaveWorkspaceAction } from '@umbraco-cms/workspace';
import type { ManifestWorkspace, ManifestWorkspaceAction, ManifestWorkspaceView } from '@umbraco-cms/models';
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/manifests.ts
index deee635772..f40c631433 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/manifests.ts
@@ -1,7 +1,9 @@
import { UmbMemberTypeRepository } from './member-type.repository';
-import { ManifestRepository } from 'libs/extensions-registry/repository.models';
+import { UmbMemberTypeStore } from './member-type.store';
+import { UmbMemberTypeTreeStore } from './member-type.tree.store';
+import type { ManifestRepository, ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
-export const MEMBER_TYPES_REPOSITORY_ALIAS = 'Umb.Repository.MemberTypes';
+export const MEMBER_TYPES_REPOSITORY_ALIAS = 'Umb.Repository.MemberType';
const repository: ManifestRepository = {
type: 'repository',
@@ -10,4 +12,21 @@ const repository: ManifestRepository = {
class: UmbMemberTypeRepository,
};
-export const manifests = [repository];
\ No newline at end of file
+export const MEMBER_TYPE_STORE_ALIAS = 'Umb.Store.MemberType';
+export const MEMBER_TYPE_TREE_STORE_ALIAS = 'Umb.Store.MemberTypeTree';
+
+const store: ManifestStore = {
+ type: 'store',
+ alias: MEMBER_TYPE_STORE_ALIAS,
+ name: 'Member Type Store',
+ class: UmbMemberTypeStore,
+};
+
+const treeStore: ManifestTreeStore = {
+ type: 'treeStore',
+ alias: MEMBER_TYPE_TREE_STORE_ALIAS,
+ name: 'Member Type Tree Store',
+ class: UmbMemberTypeTreeStore,
+};
+
+export const manifests = [store, treeStore, repository];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts
index ec294e6ee6..83120d5b3d 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts
@@ -1,6 +1,6 @@
import { MemberTypeTreeServerDataSource } from './sources/member-type.tree.server.data';
import { UmbMemberTypeTreeStore, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN } from './member-type.tree.store';
-import { UmbMemberTypeDetailStore, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from './member-type.detail.store';
+import { UmbMemberTypeStore, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN } from './member-type.store';
import { UmbMemberTypeDetailServerDataSource } from './sources/member-type.detail.server.data';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbContextConsumerController } from '@umbraco-cms/context-api';
@@ -21,7 +21,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
#treeStore?: UmbMemberTypeTreeStore;
#detailSource: UmbMemberTypeDetailServerDataSource;
- #detailStore?: UmbMemberTypeDetailStore;
+ #store?: UmbMemberTypeStore;
#notificationContext?: UmbNotificationContext;
@@ -33,8 +33,8 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
this.#detailSource = new UmbMemberTypeDetailServerDataSource(this.#host);
this.#init = Promise.all([
- new UmbContextConsumerController(this.#host, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN, (instance) => {
- this.#detailStore = instance;
+ new UmbContextConsumerController(this.#host, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN, (instance) => {
+ this.#store = instance;
}),
new UmbContextConsumerController(this.#host, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => {
@@ -123,7 +123,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
const { data, error } = await this.#detailSource.requestByKey(key);
if (data) {
- this.#detailStore?.append(data);
+ this.#store?.append(data);
}
return { data, error };
}
@@ -146,7 +146,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server.
// Consider notify a workspace if a member type is deleted from the store while someone is editing it.
- this.#detailStore?.remove([key]);
+ this.#store?.remove([key]);
this.#treeStore?.removeItem(key);
// TODO: would be nice to align the stores on methods/methodNames.
@@ -173,7 +173,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server
// Consider notify a workspace if a member type is updated in the store while someone is editing it.
- this.#detailStore?.append(detail);
+ this.#store?.append(detail);
this.#treeStore?.updateItem(detail.key, { name: detail.name });
// TODO: would be nice to align the stores on methods/methodNames.
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.store.ts
similarity index 63%
rename from src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.detail.store.ts
rename to src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.store.ts
index ebeb8af837..c13dacc56f 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.detail.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.store.ts
@@ -6,17 +6,15 @@ import type { MemberTypeDetails } from '@umbraco-cms/models';
/**
* @export
- * @class UmbMemberTypeDetailStore
+ * @class UmbMemberTypeStore
* @extends {UmbStoreBase}
- * @description - Details Data Store for Member Types
+ * @description - Data Store for Member Types
*/
-export class UmbMemberTypeDetailStore
- extends UmbStoreBase
-{
+export class UmbMemberTypeStore extends UmbStoreBase {
#data = new ArrayState([], (x) => x.key);
constructor(host: UmbControllerHostInterface) {
- super(host, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString());
+ super(host, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN.toString());
}
append(MemberType: MemberTypeDetails) {
@@ -28,6 +26,4 @@ export class UmbMemberTypeDetailStore
}
}
-export const UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken(
- 'UmbMemberTypeDetailStore'
-);
+export const UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMemberTypeStore');
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.tree.store.ts
index 3b87c4988f..d10cf2953e 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.tree.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.tree.store.ts
@@ -9,7 +9,6 @@ import type { UmbControllerHostInterface } from '@umbraco-cms/controller';
* @description - Tree Data Store for Member Types
*/
export class UmbMemberTypeTreeStore extends UmbTreeStoreBase {
-
constructor(host: UmbControllerHostInterface) {
super(host, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN.toString());
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/entity-actions/manifests.ts
index 6f040a256b..57654c41d0 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/entity-actions/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/entity-actions/manifests.ts
@@ -1,3 +1,4 @@
+import { MEMBER_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbDeleteEntityAction } from '@umbraco-cms/entity-action';
import { ManifestEntityAction } from 'libs/extensions-registry/entity-action.models';
@@ -11,7 +12,7 @@ const entityActions: Array = [
icon: 'umb:trash',
label: 'Delete',
api: UmbDeleteEntityAction,
- repositoryAlias: 'Umb.Repository.Member',
+ repositoryAlias: MEMBER_REPOSITORY_ALIAS,
},
},
];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/member.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/member.detail.store.ts
index bb665251ad..0a79950126 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/member.detail.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/member.detail.store.ts
@@ -6,33 +6,29 @@ import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store';
import { umbMemberData } from 'src/core/mocks/data/member.data';
-export const UMB_MEMBER_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMemberDetailStore');
-
/**
* @export
- * @class UmbMemberDetailStore
+ * @class UmbMemberStore
* @extends {UmbStoreBase}
- * @description - Detail Data Store for Members
+ * @description - Data Store for Members
*/
-export class UmbMemberDetailStore extends UmbStoreBase implements UmbEntityDetailStore {
-
- #data = new ArrayState([], x => x.key);
+export class UmbMemberStore extends UmbStoreBase implements UmbEntityDetailStore {
+ #data = new ArrayState([], (x) => x.key);
public groups = this.#data.asObservable();
constructor(private host: UmbControllerHostInterface) {
- super(host, UMB_MEMBER_DETAIL_STORE_CONTEXT_TOKEN.toString());
+ super(host, UMB_MEMBER_STORE_CONTEXT_TOKEN.toString());
}
getScaffold(entityType: string, parentKey: string | null) {
- return {
- } as MemberDetails;
+ return {} as MemberDetails;
}
/**
* @description - Request a Member by key. The Member is added to the store and is returned as an Observable.
* @param {string} key
* @return {*} {(Observable)}
- * @memberof UmbMemberDetailStore
+ * @memberof UmbMemberStore
*/
getByKey(key: string): Observable {
// tryExecuteAndNotify(this.host, MemberResource.getMemberByKey({ key })).then(({ data }) => {
@@ -47,13 +43,12 @@ export class UmbMemberDetailStore extends UmbStoreBase implements UmbEntityDetai
this.#data.appendOne(member);
}
- return createObservablePart(
- this.#data,
- (members) => members.find((member) => member.key === key) as MemberDetails
- );
+ return createObservablePart(this.#data, (members) => members.find((member) => member.key === key) as MemberDetails);
}
async save(member: Array): Promise {
return null as any;
}
}
+
+export const UMB_MEMBER_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMemberStore');
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/manifests.ts
index cce97581b9..a2e150c0ca 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/manifests.ts
@@ -1,5 +1,8 @@
import { UmbMemberRepository } from './member.repository';
+import { UmbMemberStore } from './member.store';
+import { UmbMemberTreeStore } from './member.tree.store';
import { ManifestRepository } from 'libs/extensions-registry/repository.models';
+import { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
export const MEMBER_REPOSITORY_ALIAS = 'Umb.Repository.Member';
@@ -10,4 +13,21 @@ const repository: ManifestRepository = {
class: UmbMemberRepository,
};
-export const manifests = [repository];
+export const MEMBER_STORE_ALIAS = 'Umb.Store.Member';
+export const MEMBER_TREE_STORE_ALIAS = 'Umb.Store.MemberTree';
+
+const store: ManifestStore = {
+ type: 'store',
+ alias: MEMBER_STORE_ALIAS,
+ name: 'Member Store',
+ class: UmbMemberStore,
+};
+
+const treeStore: ManifestTreeStore = {
+ type: 'treeStore',
+ alias: MEMBER_TREE_STORE_ALIAS,
+ name: 'Member Tree Store',
+ class: UmbMemberTreeStore,
+};
+
+export const manifests = [store, treeStore, repository];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.repository.ts
index db2744a5ed..74b1d752fe 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.repository.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.repository.ts
@@ -73,7 +73,7 @@ export class UmbMemberRepository implements UmbTreeRepository {
async rootTreeItems() {
await this.#init;
- return this.#treeStore!.rootItems();
+ return this.#treeStore!.rootItems;
}
async treeItemsOf(parentKey: string | null) {
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.store.ts
new file mode 100644
index 0000000000..991ada05f4
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.store.ts
@@ -0,0 +1,29 @@
+import { UmbContextToken } from '@umbraco-cms/context-api';
+import { UmbStoreBase } from '@umbraco-cms/store';
+import { UmbControllerHostInterface } from '@umbraco-cms/controller';
+import { ArrayState } from '@umbraco-cms/observable-api';
+import type { MemberDetails } from '@umbraco-cms/models';
+
+/**
+ * @export
+ * @class UmbMemberStore
+ * @extends {UmbStoreBase}
+ * @description - Data Store for Members
+ */
+export class UmbMemberStore extends UmbStoreBase {
+ #data = new ArrayState([], (x) => x.key);
+
+ constructor(host: UmbControllerHostInterface) {
+ super(host, UMB_MEMBER_STORE_CONTEXT_TOKEN.toString());
+ }
+
+ append(member: MemberDetails) {
+ this.#data.append([member]);
+ }
+
+ remove(uniques: string[]) {
+ this.#data.remove(uniques);
+ }
+}
+
+export const UMB_MEMBER_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMemberStore');
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.tree.store.ts
index c3ea8a4e18..6dd095fded 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.tree.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.tree.store.ts
@@ -1,7 +1,5 @@
-import type { EntityTreeItemModel } from '@umbraco-cms/backend-api';
import { UmbContextToken } from '@umbraco-cms/context-api';
-import { ArrayState } from '@umbraco-cms/observable-api';
-import { UmbStoreBase } from '@umbraco-cms/store';
+import { UmbTreeStoreBase } from '@umbraco-cms/store';
import type { UmbControllerHostInterface } from '@umbraco-cms/controller';
export const UMB_MEMBER_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMemberTreeStore');
@@ -9,12 +7,10 @@ export const UMB_MEMBER_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken([], (x) => x.key);
-
+export class UmbMemberTreeStore extends UmbTreeStoreBase {
/**
* Creates an instance of UmbTemplateTreeStore.
* @param {UmbControllerHostInterface} host
@@ -23,71 +19,4 @@ export class UmbMemberTreeStore extends UmbStoreBase {
constructor(host: UmbControllerHostInterface) {
super(host, UMB_MEMBER_TREE_STORE_CONTEXT_TOKEN.toString());
}
-
- /**
- * Appends items to the store
- * @param {Array} items
- * @memberof UmbTemplateTreeStore
- */
- appendItems(items: Array) {
- this.#data.append(items);
- }
-
- /**
- * Updates an item in the store
- * @param {string} key
- * @param {Partial} data
- * @memberof UmbMemberGroupTreeStore
- */
- updateItem(key: string, data: Partial) {
- const entries = this.#data.getValue();
- const entry = entries.find((entry) => entry.key === key);
-
- if (entry) {
- this.#data.appendOne({ ...entry, ...data });
- }
- }
-
- /**
- * Removes an item from the store
- * @param {string} key
- * @memberof UmbMemberGroupTreeStore
- */
- removeItem(key: string) {
- const entries = this.#data.getValue();
- const entry = entries.find((entry) => entry.key === key);
-
- if (entry) {
- this.#data.remove([key]);
- }
- }
-
- /**
- * Returns an observable to observe the root items
- * @return {*}
- * @memberof UmbMemberGroupTreeStore
- */
- rootItems() {
- return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null));
- }
-
- /**
- * Returns an observable to observe the children of a given parent
- * @param {(string | null)} parentKey
- * @return {*}
- * @memberof UmbMemberGroupTreeStore
- */
- childrenOf(parentKey: string | null) {
- return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === parentKey));
- }
-
- /**
- * Returns an observable to observe the items with the given keys
- * @param {Array} keys
- * @return {*}
- * @memberof UmbMemberGroupTreeStore
- */
- items(keys: Array) {
- return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? '')));
- }
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/sources/member.tree.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/sources/member.tree.server.data.ts
index 4a73347356..4f4fce08c8 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/sources/member.tree.server.data.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/sources/member.tree.server.data.ts
@@ -1,5 +1,4 @@
import { MemberTreeDataSource } from '.';
-import { ProblemDetailsModel } from '@umbraco-cms/backend-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
/**
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts
index b7cb38e6ec..a032a5fefc 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts
@@ -1,7 +1,7 @@
import { UmbEntityWorkspaceManager } from '../../../shared/components/workspace/workspace-context/entity-manager-controller';
import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context';
import { UmbWorkspaceEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-entity-context.interface';
-import { UMB_MEMBER_DETAIL_STORE_CONTEXT_TOKEN } from '../member.detail.store';
+import { UMB_MEMBER_STORE_CONTEXT_TOKEN } from '../member.detail.store';
import { UmbMemberRepository } from '../repository/member.repository';
import type { MemberDetails } from '@umbraco-cms/models';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
@@ -10,7 +10,7 @@ export class UmbWorkspaceMemberContext
extends UmbWorkspaceContext
implements UmbWorkspaceEntityContextInterface
{
- #manager = new UmbEntityWorkspaceManager(this.host, 'member', UMB_MEMBER_DETAIL_STORE_CONTEXT_TOKEN);
+ #manager = new UmbEntityWorkspaceManager(this.host, 'member', UMB_MEMBER_STORE_CONTEXT_TOKEN);
public readonly data = this.#manager.state.asObservable();
public readonly name = this.#manager.state.getObservablePart((state) => state?.name);
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/packages/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/packages/index.ts
index 7a1e274f75..f0a7107779 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/packages/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/packages/index.ts
@@ -1,3 +1,4 @@
+import { manifests as repositoryManifests } from './repository/manifests';
import { manifests as packageBuilderManifests } from './package-builder/manifests';
import { manifests as packageRepoManifests } from './package-repo/manifests';
import { manifests as packageSectionManifests } from './package-section/manifests';
@@ -5,11 +6,15 @@ import { manifests as packageSectionManifests } from './package-section/manifest
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTypes } from '@umbraco-cms/extensions-registry';
+export const manifests = [
+ ...repositoryManifests,
+ ...packageBuilderManifests,
+ ...packageRepoManifests,
+ ...packageSectionManifests,
+];
+
const registerExtensions = (manifests: Array) => {
- manifests.forEach((manifest) => {
- if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
- umbExtensionsRegistry.register(manifest);
- });
+ manifests.forEach((manifest) => umbExtensionsRegistry.register(manifest));
};
-registerExtensions([...packageBuilderManifests, ...packageRepoManifests, ...packageSectionManifests]);
+registerExtensions(manifests);
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/created/packages-created-overview.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/created/packages-created-overview.element.ts
index 20b33ad3f8..0d23f0c4ef 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/created/packages-created-overview.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/created/packages-created-overview.element.ts
@@ -7,6 +7,7 @@ import { PackageDefinitionModel, PackageResource } from '@umbraco-cms/backend-ap
import { UmbLitElement } from '@umbraco-cms/element';
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
+import { UMB_CONFIRM_MODAL_TOKEN } from 'src/backoffice/shared/modals/confirm';
@customElement('umb-packages-created-overview')
export class UmbPackagesCreatedOverviewElement extends UmbLitElement {
@@ -134,18 +135,14 @@ export class UmbPackagesCreatedOverviewElement extends UmbLitElement {
async #deletePackage(p: PackageDefinitionModel) {
if (!p.key) return;
- const modalHandler = this._modalContext?.confirm({
+ const modalHandler = this._modalContext?.open(UMB_CONFIRM_MODAL_TOKEN, {
color: 'danger',
headline: `Remove ${p.name}?`,
content: 'Are you sure you want to delete this package',
confirmLabel: 'Delete',
});
- const deleteConfirmed = await modalHandler?.onClose().then(({ confirmed }: any) => {
- return confirmed;
- });
-
- if (!deleteConfirmed == true) return;
+ await modalHandler?.onSubmit();
const { error } = await tryExecuteAndNotify(this, PackageResource.deletePackageCreatedByKey({ key: p.key }));
if (error) return;
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view-item.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view-item.element.ts
index 3df5afd999..e8ff26a0dd 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view-item.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view-item.element.ts
@@ -4,7 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js';
import { firstValueFrom, map } from 'rxjs';
import { UUIButtonState } from '@umbraco-ui/uui';
-import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '../../../../../core/modal';
+import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
import { createExtensionElement, umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import type { ManifestPackageView } from '@umbraco-cms/models';
@@ -12,6 +12,7 @@ import { UmbLitElement } from '@umbraco-cms/element';
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
import { PackageResource } from '@umbraco-cms/backend-api';
import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/notification';
+import { UMB_CONFIRM_MODAL_TOKEN } from 'src/backoffice/shared/modals/confirm';
@customElement('umb-installed-packages-section-view-item')
export class UmbInstalledPackagesSectionViewItem extends UmbLitElement {
@@ -81,18 +82,15 @@ export class UmbInstalledPackagesSectionViewItem extends UmbLitElement {
async _onMigration() {
if (!this.name) return;
- const modalHandler = this._modalContext?.confirm({
+ const modalHandler = this._modalContext?.open(UMB_CONFIRM_MODAL_TOKEN, {
color: 'positive',
headline: `Run migrations for ${this.name}?`,
content: `Do you want to start run migrations for ${this.name}`,
confirmLabel: 'Run migrations',
});
- const migrationConfirmed = await modalHandler?.onClose().then(({ confirmed }: any) => {
- return confirmed;
- });
+ await modalHandler?.onSubmit();
- if (!migrationConfirmed == true) return;
this._migrationButtonState = 'waiting';
const { error } = await tryExecuteAndNotify(
this,
@@ -141,11 +139,15 @@ export class UmbInstalledPackagesSectionViewItem extends UmbLitElement {
return;
}
+ // TODO: add dedicated modal for package views, and register it in a manifest.
+ alert('package view modal temporarily disabled. See comment in code.');
+ /*
this._modalContext?.open(element, {
data: { name: this.name, version: this.version },
size: 'full',
type: 'sidebar',
});
+ */
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view.element.ts
index 08449e0cad..7880202c02 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view.element.ts
@@ -1,4 +1,4 @@
-import { html, css, nothing } from 'lit';
+import { html, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import { combineLatest } from 'rxjs';
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/packages/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/packages/repository/manifests.ts
new file mode 100644
index 0000000000..ab052945f7
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/packages/repository/manifests.ts
@@ -0,0 +1,24 @@
+import { UmbPackageRepository } from './package.repository';
+import { UmbPackageStore } from './package.store';
+import { ManifestRepository } from 'libs/extensions-registry/repository.models';
+import { ManifestStore } from '@umbraco-cms/extensions-registry';
+
+export const PACKAGE_REPOSITORY_ALIAS = 'Umb.Repository.Package';
+
+const repository: ManifestRepository = {
+ type: 'repository',
+ alias: PACKAGE_REPOSITORY_ALIAS,
+ name: 'Package Repository',
+ class: UmbPackageRepository,
+};
+
+export const PACKAGE_STORE_ALIAS = 'Umb.Store.Package';
+
+const store: ManifestStore = {
+ type: 'store',
+ alias: PACKAGE_STORE_ALIAS,
+ name: 'Package Store',
+ class: UmbPackageStore,
+};
+
+export const manifests = [store, repository];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/search/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/search/index.ts
index 01360ce456..e0d341e7cc 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/search/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/search/index.ts
@@ -3,11 +3,10 @@ import { manifests as searchManifests } from '../search/manifests';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTypes } from '@umbraco-cms/extensions-registry';
+export const manifests = [...searchManifests];
+
const registerExtensions = (manifests: Array) => {
- manifests.forEach((manifest) => {
- if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
- umbExtensionsRegistry.register(manifest);
- });
+ manifests.forEach((manifest) => umbExtensionsRegistry.register(manifest));
};
-registerExtensions([...searchManifests]);
+registerExtensions(manifests);
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/search/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/search/modals/manifests.ts
new file mode 100644
index 0000000000..7765c306fc
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/search/modals/manifests.ts
@@ -0,0 +1,12 @@
+import type { ManifestModal } from '@umbraco-cms/extensions-registry';
+
+const modals: Array = [
+ {
+ type: 'modal',
+ alias: 'Umb.Modal.Search',
+ name: 'Search Modal',
+ loader: () => import('./search/search-modal.element'),
+ },
+];
+
+export const manifests = [...modals];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/search/modals/search/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/search/modals/search/index.ts
new file mode 100644
index 0000000000..9859d03434
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/search/modals/search/index.ts
@@ -0,0 +1,3 @@
+import { UmbModalToken } from '@umbraco-cms/modal';
+
+export const UMB_SEARCH_MODAL_TOKEN = new UmbModalToken('Umb.Modal.Search');
diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/search/modal-layout-search.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/search/modals/search/search-modal.element.ts
similarity index 97%
rename from src/Umbraco.Web.UI.Client/src/core/modal/layouts/search/modal-layout-search.element.ts
rename to src/Umbraco.Web.UI.Client/src/backoffice/search/modals/search/search-modal.element.ts
index df4ef06d2f..b294c49eda 100644
--- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/search/modal-layout-search.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/search/modals/search/search-modal.element.ts
@@ -14,8 +14,8 @@ export type SearchGroupItem = {
name: string;
items: Array;
};
-@customElement('umb-modal-layout-search')
-export class UmbModalLayoutSearchElement extends LitElement {
+@customElement('umb-search-modal')
+export class UmbSearchModalElement extends LitElement {
static styles = [
UUITextStyles,
css`
@@ -308,10 +308,10 @@ export class UmbModalLayoutSearchElement extends LitElement {
];
}
-export default UmbModalLayoutSearchElement;
+export default UmbSearchModalElement;
declare global {
interface HTMLElementTagNameMap {
- 'umb-modal-layout-search': UmbModalLayoutSearchElement;
+ 'umb-search-modal': UmbSearchModalElement;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/manifests.ts
index 0bc2ae18bd..7502a562d3 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/manifests.ts
@@ -1,7 +1,7 @@
import { UmbCultureRepository } from '../repository/culture.repository';
import { ManifestRepository } from 'libs/extensions-registry/repository.models';
-export const CULTURE_REPOSITORY_ALIAS = 'Umb.Repository.Cultures';
+export const CULTURE_REPOSITORY_ALIAS = 'Umb.Repository.Culture';
const repository: ManifestRepository = {
type: 'repository',
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/modal-views/fields-settings.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/modal-views/fields-settings.element.ts
index ce43b2b8cd..8da8cf647e 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/modal-views/fields-settings.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/modal-views/fields-settings.element.ts
@@ -1,15 +1,14 @@
import { html, css } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, state } from 'lit/decorators.js';
-import { UmbModalLayoutElement } from '../../../../../../core/modal';
+import { UmbCreateDocumentModalResultData, UmbExamineFieldsSettingsModalData } from '.';
+import { UmbModalBaseElement } from '@umbraco-cms/modal';
-export interface UmbModalFieldsSettingsData {
- name: string;
- exposed: boolean;
-}
-
-@customElement('umb-modal-layout-fields-settings')
-export class UmbModalLayoutFieldsSettingsElement extends UmbModalLayoutElement {
+@customElement('umb-examine-fields-settings-modal')
+export class UmbExamineFieldsSettingsModalElement extends UmbModalBaseElement<
+ UmbExamineFieldsSettingsModalData,
+ UmbCreateDocumentModalResultData
+> {
static styles = [
UUITextStyles,
css`
@@ -44,10 +43,10 @@ export class UmbModalLayoutFieldsSettingsElement extends UmbModalLayoutElement {
- return { name: field.name, exposed: field.exposed };
- }))
- : '';
+ this._fields =
+ this.data?.map((field) => {
+ return { name: field.name, exposed: field.exposed };
+ }) || undefined;
}
render() {
@@ -92,6 +90,6 @@ export class UmbModalLayoutFieldsSettingsElement extends UmbModalLayoutElement {
+@customElement('umb-modal-element-fields-viewer')
+export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement {
static styles = [
UUITextStyles,
css`
@@ -16,7 +16,6 @@ export class UmbModalLayoutFieldsViewerElement extends UmbModalLayoutElement;
+
+export interface UmbCreateDocumentModalResultData {
+ fields?: UmbExamineFieldsSettingsModalData;
+}
+
+export const UMB_EXAMINE_FIELDS_SETTINGS_MODAL_TOKEN = new UmbModalToken<
+ UmbExamineFieldsSettingsModalData,
+ UmbCreateDocumentModalResultData
+>('Umb.Modal.ExamineFieldsSettings', {
+ type: 'sidebar',
+ size: 'small',
+});
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-indexers.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-indexers.ts
index 34c20af5d2..1fcfb4384d 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-indexers.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-indexers.ts
@@ -1,16 +1,14 @@
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
-
import { UUIButtonState } from '@umbraco-ui/uui-button';
-
-import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '../../../../../core/modal';
-
-import './section-view-examine-searchers';
-
+import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
import { HealthStatusModel, IndexModel, IndexerResource } from '@umbraco-cms/backend-api';
import { UmbLitElement } from '@umbraco-cms/element';
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
+import { UMB_CONFIRM_MODAL_TOKEN } from 'src/backoffice/shared/modals/confirm';
+
+import './section-view-examine-searchers';
@customElement('umb-dashboard-examine-index')
export class UmbDashboardExamineIndexElement extends UmbLitElement {
@@ -120,7 +118,7 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
}
private async _onRebuildHandler() {
- const modalHandler = this._modalContext?.confirm({
+ const modalHandler = this._modalContext?.open(UMB_CONFIRM_MODAL_TOKEN, {
headline: `Rebuild ${this.indexName}`,
content: html`
This will cause the index to be rebuilt.
@@ -131,8 +129,8 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
color: 'danger',
confirmLabel: 'Rebuild',
});
- modalHandler?.onClose().then(({ confirmed }) => {
- if (confirmed) this._rebuild();
+ modalHandler?.onSubmit().then(() => {
+ this._rebuild();
});
}
private async _rebuild() {
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-searchers.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-searchers.ts
index 941f79b998..fd7f1b6f14 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-searchers.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-searchers.ts
@@ -1,15 +1,14 @@
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, nothing } from 'lit';
import { customElement, state, query, property } from 'lit/decorators.js';
-
-import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '../../../../../core/modal';
-
+import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
import { SearchResultModel, SearcherResource, FieldModel } from '@umbraco-cms/backend-api';
import { UmbLitElement } from '@umbraco-cms/element';
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
import './modal-views/fields-viewer.element';
import './modal-views/fields-settings.element';
+import { UMB_EXAMINE_FIELDS_SETTINGS_MODAL_TOKEN } from './modal-views';
interface ExposedSearchResultField {
name?: string | null;
@@ -175,12 +174,10 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement {
}
private _onFieldFilterClick() {
- const modalHandler = this._modalContext?.open('umb-modal-layout-fields-settings', {
- type: 'sidebar',
- size: 'small',
- data: { ...this._exposedFields },
+ const modalHandler = this._modalContext?.open(UMB_EXAMINE_FIELDS_SETTINGS_MODAL_TOKEN, {
+ ...this._exposedFields,
});
- modalHandler?.onClose().then(({ fields } = {}) => {
+ modalHandler?.onSubmit().then(({ fields } = {}) => {
if (!fields) return;
this._exposedFields = fields;
});
@@ -241,7 +238,7 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement {
look="secondary"
label="Open sidebar to see all fields"
@click="${() =>
- this._modalContext?.open('umb-modal-layout-fields-viewer', {
+ this._modalContext?.open('umb-modal-element-fields-viewer', {
type: 'sidebar',
size: 'medium',
data: { ...rowData, name: this.getSearchResultNodeName(rowData) },
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/manifests.ts
index 16ca3c3f9b..f3475347ba 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/manifests.ts
@@ -1,4 +1,4 @@
-import type { ManifestDashboard } from '@umbraco-cms/models';
+import type { ManifestDashboard, ManifestModal } from '@umbraco-cms/models';
const dashboards: Array = [
{
@@ -94,4 +94,13 @@ const dashboards: Array = [
},
];
-export const manifests = [...dashboards];
+const modals: Array = [
+ {
+ type: 'modal',
+ alias: 'Umb.Modal.ExamineFieldsSettings',
+ name: 'Examine Field Settings Modal',
+ loader: () => import('./examine-management/views/modal-views/fields-settings.element'),
+ },
+];
+
+export const manifests = [...dashboards, ...modals];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts
index 8235fd382f..3d79b89afc 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts
@@ -2,9 +2,8 @@ import { UUIButtonState } from '@umbraco-ui/uui';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
-
-import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '../../../../core/modal';
-
+import { UMB_CONFIRM_MODAL_TOKEN } from '../../../shared/modals/confirm';
+import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
import { PublishedCacheResource } from '@umbraco-cms/backend-api';
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
import { UmbLitElement } from '@umbraco-cms/element';
@@ -82,14 +81,14 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement {
}
}
private async _onReloadCacheHandler() {
- const modalHandler = this._modalContext?.confirm({
+ const modalHandler = this._modalContext?.open(UMB_CONFIRM_MODAL_TOKEN, {
headline: 'Reload',
content: html` Trigger a in-memory and local file cache reload on all servers. `,
color: 'danger',
confirmLabel: 'Continue',
});
- modalHandler?.onClose().then(({ confirmed }) => {
- if (confirmed) this._reloadMemoryCache();
+ modalHandler?.onSubmit().then(() => {
+ this._reloadMemoryCache();
});
}
@@ -105,14 +104,14 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement {
}
private async _onRebuildCacheHandler() {
- const modalHandler = this._modalContext?.confirm({
+ const modalHandler = this._modalContext?.open(UMB_CONFIRM_MODAL_TOKEN, {
headline: 'Rebuild',
content: html` Rebuild content in cmsContentNu database table. Expensive.`,
color: 'danger',
confirmLabel: 'Continue',
});
- modalHandler?.onClose().then(({ confirmed }) => {
- if (confirmed) this._rebuildDatabaseCache();
+ modalHandler?.onSubmit().then(() => {
+ this._rebuildDatabaseCache();
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/manifests.ts
index fbe0167037..4d54186a90 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/manifests.ts
@@ -1,13 +1,33 @@
import { UmbDataTypeRepository } from '../repository/data-type.repository';
+import { UmbDataTypeStore } from './data-type.store';
+import { UmbDataTypeTreeStore } from './data-type.tree.store';
import { ManifestRepository } from 'libs/extensions-registry/repository.models';
+import { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
-export const DATA_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.DataTypes';
+export const DATA_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.DataType';
const repository: ManifestRepository = {
type: 'repository',
alias: DATA_TYPE_REPOSITORY_ALIAS,
- name: 'Data Types Repository',
+ name: 'Data Type Repository',
class: UmbDataTypeRepository,
};
-export const manifests = [repository];
+export const DATA_TYPE_STORE_ALIAS = 'Umb.Store.DataType';
+export const DATA_TYPE_TREE_STORE_ALIAS = 'Umb.Store.DataTypeTree';
+
+const store: ManifestStore = {
+ type: 'store',
+ alias: DATA_TYPE_STORE_ALIAS,
+ name: 'Data Type Store',
+ class: UmbDataTypeStore,
+};
+
+const treeStore: ManifestTreeStore = {
+ type: 'treeStore',
+ alias: DATA_TYPE_TREE_STORE_ALIAS,
+ name: 'Data Type Tree Store',
+ class: UmbDataTypeTreeStore,
+};
+
+export const manifests = [repository, store, treeStore];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/data-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/data-type-workspace-view-edit.element.ts
index c3495deef6..fd12cca7b2 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/data-type-workspace-view-edit.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/data-type-workspace-view-edit.element.ts
@@ -1,8 +1,9 @@
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, nothing } from 'lit';
import { customElement, state } from 'lit/decorators.js';
-import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '../../../../../../core/modal';
import { UmbDataTypeWorkspaceContext } from '../../data-type-workspace.context';
+import { UMB_PROPERTY_EDITOR_UI_PICKER_MODAL_TOKEN } from '../../../../../shared/property-editors/modals/property-editor-ui-picker';
+import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
import { UmbLitElement } from '@umbraco-cms/element';
import type { DataTypeModel } from '@umbraco-cms/backend-api';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
@@ -100,12 +101,11 @@ export class UmbDataTypeWorkspaceViewEditElement extends UmbLitElement {
private _openPropertyEditorUIPicker() {
if (!this._dataType) return;
- const modalHandler = this._modalContext?.propertyEditorUIPicker({
+ const modalHandler = this._modalContext?.open(UMB_PROPERTY_EDITOR_UI_PICKER_MODAL_TOKEN, {
selection: this._propertyEditorUiAlias ? [this._propertyEditorUiAlias] : [],
});
- modalHandler?.onClose().then(({ selection } = {}) => {
- if (!selection) return;
+ modalHandler?.onSubmit().then(({ selection }) => {
this._selectPropertyEditorUI(selection[0]);
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts
index 3558e546ce..f53810199b 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts
@@ -4,6 +4,7 @@ import { isManifestElementNameType, umbExtensionsRegistry } from '@umbraco-cms/e
import type { ManifestBase } from '@umbraco-cms/models';
import { UmbLitElement } from '@umbraco-cms/element';
import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
+import { UMB_CONFIRM_MODAL_TOKEN } from 'src/backoffice/shared/modals/confirm';
@customElement('umb-extension-root-workspace')
export class UmbExtensionRootWorkspaceElement extends UmbLitElement {
@@ -27,19 +28,16 @@ export class UmbExtensionRootWorkspaceElement extends UmbLitElement {
});
}
- #removeExtension(extension: ManifestBase) {
- const modalHandler = this._modalContext?.confirm({
+ async #removeExtension(extension: ManifestBase) {
+ const modalHandler = this._modalContext?.open(UMB_CONFIRM_MODAL_TOKEN, {
headline: 'Unload extension',
confirmLabel: 'Unload',
content: html`Are you sure you want to unload the extension ${extension.alias}?
`,
color: 'danger',
});
- modalHandler?.onClose().then(({ confirmed }: any) => {
- if (confirmed) {
- umbExtensionsRegistry.unregister(extension.alias);
- }
- });
+ await modalHandler?.onSubmit();
+ umbExtensionsRegistry.unregister(extension.alias);
}
render() {
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts
index f566504513..abe3d1ba75 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts
@@ -10,14 +10,7 @@ import { manifests as logviewerManifests } from './logviewer/manifests';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTypes } from '@umbraco-cms/extensions-registry';
-const registerExtensions = (manifests: Array) => {
- manifests.forEach((manifest) => {
- if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
- umbExtensionsRegistry.register(manifest);
- });
-};
-
-registerExtensions([
+export const manifests = [
...settingsSectionManifests,
...settingsMenuManifests,
...dashboardManifests,
@@ -26,4 +19,10 @@ registerExtensions([
...cultureManifests,
...languageManifests,
...logviewerManifests,
-]);
+];
+
+const registerExtensions = (manifests: Array) => {
+ manifests.forEach((manifest) => umbExtensionsRegistry.register(manifest));
+};
+
+registerExtensions(manifests);
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/entity-actions/manifests.ts
index e8846b75a6..7852f3e09c 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/entity-actions/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/entity-actions/manifests.ts
@@ -1,8 +1,8 @@
+import { LANGUAGE_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbDeleteEntityAction } from '@umbraco-cms/entity-action';
import { ManifestEntityAction } from '@umbraco-cms/extensions-registry';
const entityType = 'language';
-const repositoryAlias = 'Umb.Repository.Languages';
const entityActions: Array = [
{
@@ -11,7 +11,7 @@ const entityActions: Array = [
name: 'Delete Language Entity Action',
meta: {
entityType,
- repositoryAlias,
+ repositoryAlias: LANGUAGE_REPOSITORY_ALIAS,
icon: 'umb:trash',
label: 'Delete',
api: UmbDeleteEntityAction,
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/manifests.ts
index 4d649f5cd8..92434c12ef 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/manifests.ts
@@ -3,6 +3,7 @@ import { manifests as treeManifests } from './menu-item/manifests';
import { manifests as entityActions } from './entity-actions/manifests';
import { manifests as workspaceManifests } from './workspace/manifests';
import { manifests as appLanguageSelect } from './app-language-select/manifests';
+import { manifests as modalManifests } from './modals/manifests';
export const manifests = [
...repositoryManifests,
@@ -10,4 +11,5 @@ export const manifests = [
...treeManifests,
...workspaceManifests,
...appLanguageSelect,
+ ...modalManifests,
];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/index.ts
new file mode 100644
index 0000000000..5f551178ad
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/index.ts
@@ -0,0 +1,20 @@
+import { LanguageModel } from '@umbraco-cms/backend-api';
+import { UmbModalToken } from '@umbraco-cms/modal';
+
+export interface UmbLanguagePickerModalData {
+ multiple?: boolean;
+ selection?: Array;
+ filter?: (language: LanguageModel) => boolean;
+}
+
+export interface UmbLanguagePickerModalResult {
+ selection: Array;
+}
+
+export const UMB_LANGUAGE_PICKER_MODAL_TOKEN = new UmbModalToken<
+ UmbLanguagePickerModalData,
+ UmbLanguagePickerModalResult
+>('Umb.Modal.LanguagePicker', {
+ type: 'sidebar',
+ size: 'small',
+});
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/language-picker/language-picker-modal-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts
similarity index 79%
rename from src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/language-picker/language-picker-modal-layout.element.ts
rename to src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts
index f4f9d72886..98a4407e97 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/language-picker/language-picker-modal-layout.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts
@@ -4,17 +4,12 @@ import { customElement, state } from 'lit/decorators.js';
import { repeat } from 'lit-html/directives/repeat.js';
import { UUIMenuItemElement, UUIMenuItemEvent } from '@umbraco-ui/uui';
import { ifDefined } from 'lit-html/directives/if-defined.js';
-import { UmbLanguageRepository } from '../repository/language.repository';
-import { UmbModalLayoutPickerBase } from '../../../../core/modal/layouts/modal-layout-picker-base';
+import { UmbLanguageRepository } from '../../repository/language.repository';
+import { UmbModalElementPickerBase } from '@umbraco-cms/modal';
import { LanguageModel } from '@umbraco-cms/backend-api';
-export interface UmbLanguagePickerModalData {
- multiple: boolean;
- selection: string[];
-}
-
-@customElement('umb-language-picker-modal-layout')
-export class UmbLanguagePickerModalLayoutElement extends UmbModalLayoutPickerBase {
+@customElement('umb-language-picker-modal')
+export class UmbLanguagePickerModalElement extends UmbModalElementPickerBase {
static styles = [UUITextStyles, css``];
@state()
@@ -70,8 +65,10 @@ export class UmbLanguagePickerModalLayoutElement extends UmbModalLayoutPickerBas
}
}
+export default UmbLanguagePickerModalElement;
+
declare global {
interface HTMLElementTagNameMap {
- 'umb-language-picker-modal-layout': UmbLanguagePickerModalLayoutElement;
+ 'umb-language-picker-modal': UmbLanguagePickerModalElement;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/manifests.ts
new file mode 100644
index 0000000000..06cf317a04
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/manifests.ts
@@ -0,0 +1,12 @@
+import type { ManifestModal } from '@umbraco-cms/extensions-registry';
+
+const modals: Array = [
+ {
+ type: 'modal',
+ alias: 'Umb.Modal.LanguagePicker',
+ name: 'Language Picker Modal',
+ loader: () => import('./language-picker/language-picker-modal.element'),
+ },
+];
+
+export const manifests = [...modals];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts
index 7020f13c22..a69836165b 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts
@@ -1,7 +1,9 @@
import { UmbLanguageRepository } from '../repository/language.repository';
+import { UmbLanguageStore } from './language.store';
import { ManifestRepository } from 'libs/extensions-registry/repository.models';
+import { ManifestStore } from '@umbraco-cms/extensions-registry';
-export const LANGUAGE_REPOSITORY_ALIAS = 'Umb.Repository.Languages';
+export const LANGUAGE_REPOSITORY_ALIAS = 'Umb.Repository.Language';
const repository: ManifestRepository = {
type: 'repository',
@@ -10,4 +12,13 @@ const repository: ManifestRepository = {
class: UmbLanguageRepository,
};
-export const manifests = [repository];
+export const LANGUAGE_STORE_ALIAS = 'Umb.Store.Language';
+
+const store: ManifestStore = {
+ type: 'store',
+ alias: LANGUAGE_STORE_ALIAS,
+ name: 'Language Store',
+ class: UmbLanguageStore,
+};
+
+export const manifests = [repository, store];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/menu-item/manifests.ts
index dc7b08158f..558df963b7 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/menu-item/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/menu-item/manifests.ts
@@ -8,7 +8,7 @@ const menuItem: ManifestMenuItem = {
meta: {
label: 'Log Viewer',
icon: 'umb:box-alt',
- entityType: 'logviewer-root',
+ entityType: 'logviewer',
menus: ['Umb.Menu.Settings'],
},
};
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/repository/log-viewer.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/repository/log-viewer.repository.ts
new file mode 100644
index 0000000000..5870e28762
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/repository/log-viewer.repository.ts
@@ -0,0 +1,101 @@
+import { UmbLogMessagesServerDataSource, UmbLogSearchesServerDataSource } from './sources/log-viewer.server.data';
+import { UmbContextConsumerController } from '@umbraco-cms/context-api';
+import { UmbControllerHostInterface } from '@umbraco-cms/controller';
+import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/notification';
+import { DirectionModel, LogLevelModel } from '@umbraco-cms/backend-api';
+
+// Move to documentation / JSdoc
+/* We need to create a new instance of the repository from within the element context. We want the notifications to be displayed in the right context. */
+// element -> context -> repository -> (store) -> data source
+// All methods should be async and return a promise. Some methods might return an observable as part of the promise response.
+export class UmbLogViewerRepository {
+ #host: UmbControllerHostInterface;
+ #searchDataSource: UmbLogSearchesServerDataSource;
+ #messagesDataSource: UmbLogMessagesServerDataSource;
+ #notificationService?: UmbNotificationContext;
+ #initResolver?: () => void;
+ #initialized = false;
+
+ constructor(host: UmbControllerHostInterface) {
+ this.#host = host;
+ this.#searchDataSource = new UmbLogSearchesServerDataSource(this.#host);
+ this.#messagesDataSource = new UmbLogMessagesServerDataSource(this.#host);
+
+ new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
+ this.#notificationService = instance;
+ this.#checkIfInitialized();
+ });
+ }
+
+ #init() {
+ // TODO: This would only works with one user of this method. If two, the first one would be forgotten, but maybe its alright for now as I guess this is temporary.
+ return new Promise((resolve) => {
+ this.#initialized ? resolve() : (this.#initResolver = resolve);
+ });
+ }
+
+ #checkIfInitialized() {
+ if (this.#notificationService) {
+ this.#initialized = true;
+ this.#initResolver?.();
+ }
+ }
+
+ async getSavedSearches({ skip, take }: { skip: number; take: number }) {
+ await this.#init();
+
+ return this.#searchDataSource.getAllSavedSearches({ skip, take });
+ }
+
+ async getMessageTemplates({ skip, take }: { skip: number; take: number }) {
+ await this.#init();
+
+ return this.#messagesDataSource.getLogViewerMessageTemplate({ skip, take });
+ }
+
+ async getLogCount({ startDate, endDate }: { startDate?: string; endDate?: string }) {
+ await this.#init();
+
+ return this.#messagesDataSource.getLogViewerLevelCount({ startDate, endDate });
+ }
+
+ async getLogs({
+ skip = 0,
+ take = 100,
+ orderDirection,
+ filterExpression,
+ logLevel,
+ startDate,
+ endDate,
+ }: {
+ skip?: number;
+ take?: number;
+ orderDirection?: DirectionModel;
+ filterExpression?: string;
+ logLevel?: Array;
+ startDate?: string;
+ endDate?: string;
+ }) {
+ await this.#init();
+
+ return this.#messagesDataSource.getLogViewerLogs({
+ skip,
+ take,
+ orderDirection,
+ filterExpression,
+ logLevel,
+ startDate,
+ endDate,
+ });
+ }
+
+ async getLogLevels({ skip = 0, take = 100 }: { skip: number; take: number }) {
+ await this.#init();
+ return this.#messagesDataSource.getLogViewerLevel({ skip, take });
+ }
+
+ async getLogViewerValidateLogsSize({ startDate, endDate }: { startDate?: string; endDate?: string }) {
+ await this.#init();
+ return this.#messagesDataSource.getLogViewerValidateLogsSize({ startDate, endDate });
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/repository/sources/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/repository/sources/index.ts
new file mode 100644
index 0000000000..518a5e18af
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/repository/sources/index.ts
@@ -0,0 +1,69 @@
+import {
+ DirectionModel,
+ LogLevelCountsModel,
+ LogLevelModel,
+ PagedLoggerModel,
+ PagedLogMessageModel,
+ PagedLogTemplateModel,
+ PagedSavedLogSearchModel,
+ SavedLogSearchModel,
+} from '@umbraco-cms/backend-api';
+import type { DataSourceResponse } from '@umbraco-cms/models';
+
+
+
+export interface LogSearchDataSource {
+ getAllSavedSearches({
+ skip,
+ take,
+ }: {
+ skip?: number;
+ take?: number;
+ }): Promise>;
+ getSavedSearchByName({ name }: { name: string }): Promise>;
+ deleteSavedSearchByName({ name }: { name: string }): Promise>;
+ postLogViewerSavedSearch({
+ requestBody,
+ }: {
+ requestBody?: SavedLogSearchModel;
+ }): Promise>;
+}
+
+export interface LogMessagesDataSource {
+ getLogViewerLevel({ skip, take }: { skip?: number; take?: number }): Promise>;
+ getLogViewerLevelCount({
+ startDate,
+ endDate,
+ }: {
+ startDate?: string;
+ endDate?: string;
+ }): Promise>;
+ getLogViewerLogs({
+ skip,
+ take = 100,
+ orderDirection,
+ filterExpression,
+ logLevel,
+ startDate,
+ endDate,
+ }: {
+ skip?: number;
+ take?: number;
+ orderDirection?: DirectionModel;
+ filterExpression?: string;
+ logLevel?: Array;
+ startDate?: string;
+ endDate?: string;
+ }): Promise>;
+ getLogViewerMessageTemplate({
+ skip,
+ take = 100,
+ startDate,
+ endDate,
+ }: {
+ skip?: number;
+ take?: number;
+ startDate?: string;
+ endDate?: string;
+ }): Promise>;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/repository/sources/log-viewer.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/repository/sources/log-viewer.server.data.ts
new file mode 100644
index 0000000000..ebdf1264e5
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/repository/sources/log-viewer.server.data.ts
@@ -0,0 +1,213 @@
+import { LogMessagesDataSource, LogSearchDataSource } from '.';
+import { DirectionModel, LogLevelModel, LogViewerResource, SavedLogSearchModel } from '@umbraco-cms/backend-api';
+import { UmbControllerHostInterface } from '@umbraco-cms/controller';
+import { tryExecuteAndNotify } from '@umbraco-cms/resources';
+
+/**
+ * A data source for the log saved searches
+ * @export
+ * @class UmbLogSearchesServerDataSource
+ * @implements {TemplateDetailDataSource}
+ */
+export class UmbLogSearchesServerDataSource implements LogSearchDataSource {
+ #host: UmbControllerHostInterface;
+
+ /**
+ * Creates an instance of UmbLogSearchesServerDataSource.
+ * @param {UmbControllerHostInterface} host
+ * @memberof UmbLogSearchesServerDataSource
+ */
+ constructor(host: UmbControllerHostInterface) {
+ this.#host = host;
+ }
+
+ /**
+ * Grabs all the log viewer saved searches from the server
+ *
+ * @param {{ skip?: number; take?: number }} { skip = 0, take = 100 }
+ * @return {*}
+ * @memberof UmbLogSearchesServerDataSource
+ */
+ async getAllSavedSearches({ skip = 0, take = 100 }: { skip?: number; take?: number }) {
+ return await tryExecuteAndNotify(this.#host, LogViewerResource.getLogViewerSavedSearch({ skip, take }));
+ }
+ /**
+ * Get a log viewer saved search by name from the server
+ *
+ * @param {{ name: string }} { name }
+ * @return {*}
+ * @memberof UmbLogSearchesServerDataSource
+ */
+ async getSavedSearchByName({ name }: { name: string }) {
+ return await tryExecuteAndNotify(this.#host, LogViewerResource.getLogViewerSavedSearchByName({ name }));
+ }
+
+ /**
+ * Post a new log viewer saved search to the server
+ *
+ * @param {{ requestBody?: SavedLogSearch }} { requestBody }
+ * @return {*}
+ * @memberof UmbLogSearchesServerDataSource
+ */
+ async postLogViewerSavedSearch({ requestBody }: { requestBody?: SavedLogSearchModel }) {
+ return await tryExecuteAndNotify(this.#host, LogViewerResource.postLogViewerSavedSearch({ requestBody }));
+ }
+ /**
+ * Remove a log viewer saved search by name from the server
+ *
+ * @param {{ name: string }} { name }
+ * @return {*}
+ * @memberof UmbLogSearchesServerDataSource
+ */
+ async deleteSavedSearchByName({ name }: { name: string }) {
+ return await tryExecuteAndNotify(this.#host, LogViewerResource.deleteLogViewerSavedSearchByName({ name }));
+ }
+}
+ /**
+ * A data source for the log messages and levels
+ *
+ * @export
+ * @class UmbLogMessagesServerDataSource
+ * @implements {LogMessagesDataSource}
+ */
+ export class UmbLogMessagesServerDataSource implements LogMessagesDataSource {
+ #host: UmbControllerHostInterface;
+
+ /**
+ * Creates an instance of UmbLogMessagesServerDataSource.
+ * @param {UmbControllerHostInterface} host
+ * @memberof UmbLogMessagesServerDataSource
+ */
+ constructor(host: UmbControllerHostInterface) {
+ this.#host = host;
+ }
+
+ /**
+ * Grabs all the loggers from the server
+ *
+ * @param {{ skip?: number; take?: number }} { skip = 0, take = 100 }
+ * @return {*}
+ * @memberof UmbLogMessagesServerDataSource
+ */
+ async getLogViewerLevel({ skip = 0, take = 100 }: { skip?: number; take?: number }) {
+ return await tryExecuteAndNotify(this.#host, LogViewerResource.getLogViewerLevel({ skip, take }));
+ }
+
+ /**
+ * Grabs all the number of different log messages from the server
+ *
+ * @param {{ skip?: number; take?: number }} { skip = 0, take = 100 }
+ * @return {*}
+ * @memberof UmbLogMessagesServerDataSource
+ */
+ async getLogViewerLevelCount({ startDate, endDate }: { startDate?: string; endDate?: string }) {
+ return await tryExecuteAndNotify(
+ this.#host,
+ LogViewerResource.getLogViewerLevelCount({
+ startDate,
+ endDate,
+ })
+ );
+ }
+ /**
+ * Grabs all the log messages from the server
+ *
+ * @param {{
+ * skip?: number;
+ * take?: number;
+ * orderDirection?: DirectionModel;
+ * filterExpression?: string;
+ * logLevel?: Array;
+ * startDate?: string;
+ * endDate?: string;
+ * }} {
+ * skip = 0,
+ * take = 100,
+ * orderDirection,
+ * filterExpression,
+ * logLevel,
+ * startDate,
+ * endDate,
+ * }
+ * @return {*}
+ * @memberof UmbLogMessagesServerDataSource
+ */
+ async getLogViewerLogs({
+ skip = 0,
+ take = 100,
+ orderDirection,
+ filterExpression,
+ logLevel,
+ startDate,
+ endDate,
+ }: {
+ skip?: number;
+ take?: number;
+ orderDirection?: DirectionModel;
+ filterExpression?: string;
+ logLevel?: Array;
+ startDate?: string;
+ endDate?: string;
+ }) {
+ return await tryExecuteAndNotify(
+ this.#host,
+ LogViewerResource.getLogViewerLog({
+ skip,
+ take,
+ orderDirection,
+ filterExpression,
+ logLevel,
+ startDate,
+ endDate,
+ })
+ );
+ }
+ /**
+ * Grabs all the log message templates from the server
+ *
+ * @param {{
+ * skip?: number;
+ * take?: number;
+ * startDate?: string;
+ * endDate?: string;
+ * }} {
+ * skip,
+ * take = 100,
+ * startDate,
+ * endDate,
+ * }
+ * @return {*}
+ * @memberof UmbLogMessagesServerDataSource
+ */
+ async getLogViewerMessageTemplate({
+ skip,
+ take = 100,
+ startDate,
+ endDate,
+ }: {
+ skip?: number;
+ take?: number;
+ startDate?: string;
+ endDate?: string;
+ }) {
+ return await tryExecuteAndNotify(
+ this.#host,
+ LogViewerResource.getLogViewerMessageTemplate({
+ skip,
+ take,
+ startDate,
+ endDate,
+ })
+ );
+ }
+
+ async getLogViewerValidateLogsSize({ startDate, endDate }: { startDate?: string; endDate?: string }) {
+ return await tryExecuteAndNotify(
+ this.#host,
+ LogViewerResource.getLogViewerValidateLogsSize({
+ startDate,
+ endDate,
+ })
+ );
+ }
+ }
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/index.ts
new file mode 100644
index 0000000000..e204c32424
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/index.ts
@@ -0,0 +1,3 @@
+export * from './log-viewer-date-range-selector.element';
+export * from './log-viewer-level-tag.element';
+export * from './log-viewer-to-many-logs-warning.element';
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/log-viewer-date-range-selector.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/log-viewer-date-range-selector.element.ts
new file mode 100644
index 0000000000..cfa465cb79
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/log-viewer-date-range-selector.element.ts
@@ -0,0 +1,132 @@
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html } from 'lit';
+import { customElement, property, queryAll, state } from 'lit/decorators.js';
+import { query } from 'router-slot';
+import {
+ LogViewerDateRange,
+ UmbLogViewerWorkspaceContext,
+ UMB_APP_LOG_VIEWER_CONTEXT_TOKEN,
+} from '../../logviewer.context';
+import { UmbLitElement } from '@umbraco-cms/element';
+
+@customElement('umb-log-viewer-date-range-selector')
+export class UmbLogViewerDateRangeSelectorElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ :host {
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-space-3);
+ }
+
+ input {
+ font-family: inherit;
+ padding: var(--uui-size-1) var(--uui-size-space-3);
+ font-size: inherit;
+ color: inherit;
+ border-radius: 0;
+ box-sizing: border-box;
+ border: none;
+ background: none;
+ width: 100%;
+ height: 100%;
+ outline: none;
+ position: relative;
+ border-bottom: 2px solid transparent;
+ }
+
+ /* find out better validation for that */
+ input:out-of-range {
+ border-color: var(--uui-color-danger);
+ }
+
+ :host([horizontal]) .input-container {
+ display: flex;
+ align-items: baseline;
+ }
+ `,
+ ];
+
+ @state()
+ private _startDate = '';
+
+ @state()
+ private _endDate = '';
+
+ @queryAll('input')
+ private _inputs!: NodeListOf;
+
+ @property({ type: Boolean, reflect: true })
+ horizontal = false;
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+ constructor() {
+ super();
+ this.addEventListener('input', this.#setDates);
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#logViewerContext?.getMessageTemplates(0, 10);
+ this.#observeStuff();
+ });
+ }
+
+ #observeStuff() {
+ if (!this.#logViewerContext) return;
+ this.observe(this.#logViewerContext.dateRange, (dateRange: LogViewerDateRange) => {
+ this._startDate = dateRange?.startDate;
+ this._endDate = dateRange?.endDate;
+ });
+ }
+
+ #setDates() {
+ this._inputs.forEach((input) => {
+ if (input.id === 'start-date') {
+ this._startDate = input.value;
+ } else if (input.id === 'end-date') {
+ this._endDate = input.value;
+ }
+ });
+ const newDateRange: LogViewerDateRange = { startDate: this._startDate, endDate: this._endDate };
+ this.#logViewerContext?.setDateRange(newDateRange);
+ }
+
+ render() {
+ return html`
+
+ From:
+ {
+ (e.target as HTMLInputElement).showPicker();
+ }}
+
+ id="start-date"
+ type="date"
+ label="From"
+ .max=${this.#logViewerContext?.today ?? ''}
+ .value=${this._startDate}>
+
+
+
+ To:
+ {
+ (e.target as HTMLInputElement).showPicker();
+ }}
+ id="end-date"
+ type="date"
+ label="To"
+ .min=${this._startDate}
+ .max=${this.#logViewerContext?.today ?? ''}
+ .value=${this._endDate}>
+
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-date-range-selector': UmbLogViewerDateRangeSelectorElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/log-viewer-level-tag.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/log-viewer-level-tag.element.ts
new file mode 100644
index 0000000000..d0fc55eb86
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/log-viewer-level-tag.element.ts
@@ -0,0 +1,47 @@
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { ifDefined } from 'lit-html/directives/if-defined.js';
+import { InterfaceColor, InterfaceLook } from '@umbraco-ui/uui-base/lib/types';
+import { LogLevelModel } from '@umbraco-cms/backend-api';
+
+interface LevelMapStyles {
+ look?: InterfaceLook;
+ color?: InterfaceColor;
+ style?: string;
+}
+
+@customElement('umb-log-viewer-level-tag')
+export class UmbLogViewerLevelTagElement extends LitElement {
+ static styles = [UUITextStyles, css``];
+
+ @property()
+ level?: LogLevelModel;
+
+ levelMap: Record = {
+ Verbose: { look: 'secondary' },
+ Debug: {
+ look: 'default',
+ style: 'background-color: var(--umb-log-viewer-debug-color); color: var(--uui-color-surface)',
+ },
+ Information: { look: 'primary', color: 'positive' },
+ Warning: { look: 'primary', color: 'warning' },
+ Error: { look: 'primary', color: 'danger' },
+ Fatal: { look: 'primary' },
+ };
+
+ render() {
+ return html`${this.level}`;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-level-tag': UmbLogViewerLevelTagElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/log-viewer-to-many-logs-warning.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/log-viewer-to-many-logs-warning.element.ts
new file mode 100644
index 0000000000..b0bdb0c1ce
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/components/log-viewer-to-many-logs-warning.element.ts
@@ -0,0 +1,27 @@
+import { css, html, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+
+@customElement('umb-log-viewer-to-many-logs-warning')
+export class UmbLogViewerToManyLogsWarningElement extends LitElement {
+ static styles = [
+ css`
+ :host {
+ text-align: center;
+ }
+ `,
+ ];
+
+ render() {
+ return html`
+ Unable to view logs
+ Today's log file is too large to be viewed and would cause performance problems.
+ If you need to view the log files, narrow your date range or try opening them manually.
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-to-many-logs-warning': UmbLogViewerToManyLogsWarningElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/logviewer-root-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/logviewer-root-workspace.element.ts
index f3f77a5ded..8579ccc331 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/logviewer-root-workspace.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/logviewer-root-workspace.element.ts
@@ -1,21 +1,190 @@
-import { html, LitElement } from 'lit';
-import { customElement } from 'lit/decorators.js';
+import './components';
+import { map } from 'rxjs';
+import { css, html, nothing } from 'lit';
+import { customElement, state, property } from 'lit/decorators.js';
+import { IRoutingInfo } from 'router-slot';
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { repeat } from 'lit-html/directives/repeat.js';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../logviewer.context';
+import { UmbLitElement } from '@umbraco-cms/element';
+import { umbExtensionsRegistry, createExtensionElement } from '@umbraco-cms/extensions-api';
+import { ManifestWorkspaceView, ManifestWorkspaceViewCollection } from '@umbraco-cms/extensions-registry';
+import { UmbRouterSlotInitEvent, UmbRouterSlotChangeEvent } from '@umbraco-cms/router';
+
+//TODO make uui-input accept min and max values
+@customElement('umb-logviewer-workspace')
+export class UmbLogViewerWorkspaceElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ :host {
+ display: block;
+ width: 100%;
+ height: 100%;
+
+ --umb-log-viewer-debug-color: var(--uui-color-default-emphasis);
+ --umb-log-viewer-information-color: var(--uui-color-positive);
+ --umb-log-viewer-warning-color: var(--uui-color-warning);
+ --umb-log-viewer-error-color: var(--uui-color-danger);
+ --umb-log-viewer-fatal-color: var(--uui-color-default);
+ --umb-log-viewer-verbose-color: var(--uui-color-current);
+ }
+
+ #header {
+ display: flex;
+ padding: 0 var(--uui-size-space-6);
+ gap: var(--uui-size-space-4);
+ align-items: center;
+ }
+
+ #router-slot {
+ height: 100%;
+ }
+
+ uui-tab-group {
+ --uui-tab-divider: var(--uui-color-border);
+ border-left: 1px solid var(--uui-color-border);
+ border-right: 1px solid var(--uui-color-border);
+ }
+ `,
+ ];
+
+ private _alias = 'Umb.Workspace.LogviewerRoot';
+
+ @state()
+ private _workspaceViews: Array = [];
+
+ @state()
+ private _routes: any[] = [];
+
+ @state()
+ private _activePath?: string;
+
+ @state()
+ private _routerPath?: string;
+
+ #logViewerContext = new UmbLogViewerWorkspaceContext(this);
+
+ constructor() {
+ super();
+ this.#logViewerContext.init();
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this._observeWorkspaceViews();
+ this.provideContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, this.#logViewerContext);
+ }
+
+ load(): void {
+ // Not relevant for this workspace -added to prevent the error from popping up
+ }
+
+ private _observeWorkspaceViews() {
+ this.observe(
+ umbExtensionsRegistry
+ .extensionsOfTypes(['workspaceView'])
+ .pipe(map((extensions) => extensions.filter((extension) => extension.meta.workspaces.includes(this._alias)))),
+ (workspaceViews) => {
+ this._workspaceViews = workspaceViews;
+ this._createRoutes();
+ }
+ );
+ }
+
+ create(): void {
+ // Not relevant for this workspace
+ }
+
+ private _createRoutes() {
+ this._routes = [];
+
+ if (this._workspaceViews.length > 0) {
+ this._routes = this._workspaceViews.map((view) => {
+ return {
+ path: `${view.meta.pathname}`,
+ component: () => {
+ return createExtensionElement(view);
+ },
+ setup: (component: Promise | HTMLElement, info: IRoutingInfo) => {
+ // When its using import, we get an element, when using createExtensionElement we get a Promise.
+ if ((component as any).then) {
+ (component as any).then((el: any) => (el.manifest = view));
+ } else {
+ (component as any).manifest = view;
+ }
+ },
+ };
+ });
+
+ this._routes.push({
+ path: '**',
+ redirectTo: `${this._workspaceViews[0].meta.pathname}`,
+ });
+ }
+ }
+
+ #renderRoutes() {
+ return html`
+ ${this._routes.length > 0
+ ? html`
+ {
+ this._routerPath = event.target.absoluteRouterPath;
+ }}
+ @change=${(event: UmbRouterSlotChangeEvent) => {
+ this._activePath = event.target.localActiveViewPath;
+ }}>
+ `
+ : nothing}
+ `;
+ }
+
+ #renderViews() {
+ return html`
+ ${this._workspaceViews.length > 1
+ ? html`
+
+ ${repeat(
+ this._workspaceViews,
+ (view) => view.alias,
+ (view) => html`
+
+
+ ${view.meta.label || view.name}
+
+ `
+ )}
+
+ `
+ : nothing}
+ `;
+ }
-@customElement('umb-logviewer-root-workspace')
-export class UmbLogViewerRootWorkspaceElement extends LitElement {
render() {
return html`
-
-
LogViewer Root Workspace
-
+
+
+ ${this.#renderViews()} ${this.#renderRoutes()}
+
+
`;
}
}
-export default UmbLogViewerRootWorkspaceElement;
+export default UmbLogViewerWorkspaceElement;
declare global {
interface HTMLElementTagNameMap {
- 'umb-logviewer-root-workspace': UmbLogViewerRootWorkspaceElement;
+ 'umb-logviewer-workspace': UmbLogViewerWorkspaceElement;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/manifests.ts
index 1e06c4df7d..c740c29aa1 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer-root/manifests.ts
@@ -1,16 +1,45 @@
import type { ManifestWorkspace, ManifestWorkspaceAction, ManifestWorkspaceView } from '@umbraco-cms/models';
+const workspaceAlias = 'Umb.Workspace.LogviewerRoot';
+
const workspace: ManifestWorkspace = {
type: 'workspace',
- alias: 'Umb.Workspace.LogviewerRoot',
+ alias: workspaceAlias,
name: 'LogViewer Root Workspace',
loader: () => import('./logviewer-root-workspace.element'),
meta: {
- entityType: 'logviewer-root',
+ entityType: 'logviewer',
},
};
-const workspaceViews: Array = [];
+const workspaceViews: Array = [
+ {
+ type: 'workspaceView',
+ alias: 'Umb.WorkspaceView.Logviewer.Overview',
+ name: 'LogViewer Root Workspace Overview View',
+ loader: () => import('../views/overview/index'),
+ weight: 300,
+ meta: {
+ workspaces: [workspaceAlias],
+ label: 'Overview',
+ pathname: 'overview',
+ icon: 'umb:box-alt',
+ },
+ },
+ {
+ type: 'workspaceView',
+ alias: 'Umb.WorkspaceView.Logviewer.Search',
+ name: 'LogViewer Root Workspace Search View',
+ loader: () => import('../views/search/index'),
+ weight: 200,
+ meta: {
+ workspaces: [workspaceAlias],
+ label: 'Search',
+ pathname: 'search',
+ icon: 'umb:search',
+ },
+ },
+];
const workspaceActions: Array = [];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer.context.ts
new file mode 100644
index 0000000000..93d7896cc6
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/logviewer.context.ts
@@ -0,0 +1,253 @@
+import { UmbLogViewerRepository } from '../repository/log-viewer.repository';
+import { ArrayState, createObservablePart, DeepState, ObjectState, StringState } from '@umbraco-cms/observable-api';
+import {
+ DirectionModel,
+ LogLevelCountsModel,
+ LogLevelModel,
+ PagedLoggerModel,
+ PagedLogMessageModel,
+ PagedLogTemplateModel,
+ PagedSavedLogSearchModel,
+} from '@umbraco-cms/backend-api';
+import { UmbControllerHostInterface } from '@umbraco-cms/controller';
+import { UmbContextToken } from '@umbraco-cms/context-api';
+import { BasicState } from 'libs/observable-api/basic-state';
+
+export type PoolingInterval = 0 | 2000 | 5000 | 10000 | 20000 | 30000;
+export interface PoolingCOnfig {
+ enabled: boolean;
+ interval: PoolingInterval;
+}
+export interface LogViewerDateRange {
+ startDate: string;
+ endDate: string;
+}
+
+export class UmbLogViewerWorkspaceContext {
+ #host: UmbControllerHostInterface;
+ #repository: UmbLogViewerRepository;
+
+ get today() {
+ const today = new Date();
+ const dd = String(today.getDate()).padStart(2, '0');
+ const mm = String(today.getMonth() + 1).padStart(2, '0');
+ const yyyy = today.getFullYear();
+
+ return yyyy + '-' + mm + '-' + dd;
+ }
+
+ get yesterday() {
+ const yesterday = new Date(new Date().setDate(new Date().getDate() - 1));
+ const dd = String(yesterday.getDate()).padStart(2, '0');
+ const mm = String(yesterday.getMonth() + 1).padStart(2, '0');
+ const yyyy = yesterday.getFullYear();
+
+ return yyyy + '-' + mm + '-' + dd;
+ }
+
+ defaultDateRange: LogViewerDateRange = {
+ startDate: this.yesterday,
+ endDate: this.today,
+ };
+
+ #savedSearches = new DeepState(undefined);
+ savedSearches = createObservablePart(this.#savedSearches, (data) => data?.items);
+
+ #logCount = new DeepState(null);
+ logCount = createObservablePart(this.#logCount, (data) => data);
+
+ #dateRange = new DeepState(this.defaultDateRange);
+ dateRange = createObservablePart(this.#dateRange, (data) => data);
+
+ #loggers = new DeepState(null);
+ loggers = createObservablePart(this.#loggers, (data) => data?.items);
+
+ #canShowLogs = new BasicState(null);
+ canShowLogs = createObservablePart(this.#canShowLogs, (data) => data);
+
+ #filterExpression = new StringState('');
+ filterExpression = createObservablePart(this.#filterExpression, (data) => data);
+
+ #messageTemplates = new DeepState(null);
+ messageTemplates = createObservablePart(this.#messageTemplates, (data) => data);
+
+ #logLevelsFilter = new ArrayState([]);
+ logLevelsFilter = createObservablePart(this.#logLevelsFilter, (data) => data);
+
+ #logs = new DeepState(null);
+ logs = createObservablePart(this.#logs, (data) => data?.items);
+ logsTotal = createObservablePart(this.#logs, (data) => data?.total);
+
+ #polling = new ObjectState({ enabled: false, interval: 2000 });
+ polling = createObservablePart(this.#polling, (data) => data);
+
+ #sortingDirection = new BasicState(DirectionModel.ASCENDING);
+ sortingDirection = createObservablePart(this.#sortingDirection, (data) => data);
+
+ #intervalID: number | null = null;
+
+ currentPage = 1;
+
+ constructor(host: UmbControllerHostInterface) {
+ this.#host = host;
+ this.#repository = new UmbLogViewerRepository(this.#host);
+ }
+
+ async init() {
+ this.validateLogSize();
+ }
+
+ setDateRange(dateRange: LogViewerDateRange) {
+ const { startDate, endDate } = dateRange;
+
+ const isAnyDateInTheFuture = new Date(startDate) > new Date() || new Date(endDate) > new Date();
+ const isStartDateBiggerThenEndDate = new Date(startDate) > new Date(endDate);
+ if (isAnyDateInTheFuture || isStartDateBiggerThenEndDate) {
+ return;
+ }
+
+ this.#dateRange.next(dateRange);
+ this.validateLogSize();
+ this.getLogCount();
+ }
+
+ async getSavedSearches() {
+ const { data } = await this.#repository.getSavedSearches({ skip: 0, take: 100 });
+ if (data) {
+ this.#savedSearches.next(data);
+ } else {
+ //falback to some default searches like in the old backoffice
+ this.#savedSearches.next({
+ items: [
+ {
+ name: 'Find all logs where the Level is NOT Verbose and NOT Debug',
+ query: "Not(@Level='Verbose') and Not(@Level='Debug')",
+ },
+ {
+ name: 'Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)',
+ query: 'Has(@Exception)',
+ },
+ {
+ name: "Find all logs that have the property 'Duration'",
+ query: 'Has(Duration)',
+ },
+ {
+ name: "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
+ query: 'Has(Duration) and Duration > 1000',
+ },
+ {
+ name: "Find all logs that are from the namespace 'Umbraco.Core'",
+ query: "StartsWith(SourceContext, 'Umbraco.Core')",
+ },
+ {
+ name: 'Find all logs that use a specific log message template',
+ query: "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'",
+ },
+ ],
+ total: 6,
+ });
+ }
+ }
+
+ async getLogCount() {
+ const { data } = await this.#repository.getLogCount({ ...this.#dateRange.getValue() });
+
+ if (data) {
+ this.#logCount.next(data);
+ }
+ }
+
+ async getMessageTemplates(skip: number, take: number) {
+ const { data } = await this.#repository.getMessageTemplates({ skip, take });
+
+ if (data) {
+ this.#messageTemplates.next(data);
+ }
+ }
+
+ async getLogLevels(skip: number, take: number) {
+ const { data } = await this.#repository.getLogLevels({ skip, take });
+
+ if (data) {
+ this.#loggers.next(data);
+ }
+ }
+
+ async validateLogSize() {
+ const { data, error } = await this.#repository.getLogViewerValidateLogsSize({ ...this.#dateRange.getValue() });
+ if (error) {
+ this.#canShowLogs.next(false);
+ console.info('LogViewer: ', error);
+ return;
+ }
+ this.#canShowLogs.next(true);
+ console.info('LogViewer:showinfg logs');
+ }
+
+ setCurrentPage(page: number) {
+ this.currentPage = page;
+ }
+
+ getLogs = async () => {
+ if (!this.#canShowLogs.getValue()) {
+ return;
+ }
+
+ const skip = (this.currentPage - 1) * 100;
+ const take = 100;
+
+ const options = {
+ skip,
+ take,
+ orderDirection: this.#sortingDirection.getValue(),
+ filterExpression: this.#filterExpression.getValue(),
+ logLevel: this.#logLevelsFilter.getValue(),
+ ...this.#dateRange.getValue(),
+ };
+
+ const { data } = await this.#repository.getLogs(options);
+
+ if (data) {
+ this.#logs.next(data);
+ }
+ };
+
+ setFilterExpression(query: string) {
+ this.#filterExpression.next(query);
+ }
+
+ setLogLevelsFilter(logLevels: LogLevelModel[]) {
+ this.#logLevelsFilter.next(logLevels);
+ }
+
+ togglePolling() {
+ const isEnabled = !this.#polling.getValue().enabled;
+ this.#polling.update({
+ enabled: isEnabled,
+ });
+
+ if (isEnabled) {
+ this.#intervalID = setInterval(() => {
+ this.currentPage = 1;
+ this.getLogs();
+ }, this.#polling.getValue().interval) as unknown as number;
+ return;
+ }
+
+ clearInterval(this.#intervalID as number);
+ }
+
+ setPollingInterval(interval: PoolingInterval) {
+ this.#polling.update({ interval, enabled: true });
+ }
+
+ toggleSortOrder() {
+ const direction = this.#sortingDirection.getValue();
+ const newDirection = direction === DirectionModel.ASCENDING ? DirectionModel.DESCENDING : DirectionModel.ASCENDING;
+ this.#sortingDirection.next(newDirection);
+ }
+}
+
+export const UMB_APP_LOG_VIEWER_CONTEXT_TOKEN = new UmbContextToken(
+ UmbLogViewerWorkspaceContext.name
+);
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/manifests.ts
index 4f3d6db1e4..5a8c3cc38d 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/manifests.ts
@@ -1,3 +1,4 @@
import { manifests as logviewerRootManifests } from './logviewer-root/manifests';
+
export const manifests = [...logviewerRootManifests];
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/index.ts
new file mode 100644
index 0000000000..926372cc6e
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/index.ts
@@ -0,0 +1,4 @@
+export * from './log-viewer-saved-searches-overview.element';
+export * from './log-viewer-message-templates-overview.element';
+export * from './log-viewer-log-types-chart.element';
+export * from './log-viewer-log-level-overview.element';
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-log-level-overview.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-log-level-overview.element.ts
new file mode 100644
index 0000000000..2aeafb27fa
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-log-level-overview.element.ts
@@ -0,0 +1,48 @@
+import { html } from 'lit';
+import { customElement, property, state } from 'lit/decorators.js';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../../../logviewer.context';
+import { UmbLitElement } from '@umbraco-cms/element';
+import { LoggerModel } from '@umbraco-cms/backend-api';
+
+//TODO: implement the saved searches pagination when the API total bug is fixed
+@customElement('umb-log-viewer-log-level-overview')
+export class UmbLogViewerLogLevelOverviewElement extends UmbLitElement {
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#logViewerContext?.getSavedSearches();
+ this.#observeLogLevels();
+ });
+ }
+
+ @state()
+ private _loggers: LoggerModel[] = [];
+ /**
+ * The name of the logger to get the level for. Defaults to 'Global'.
+ *
+ * @memberof UmbLogViewerLogLevelOverviewElement
+ */
+ @property()
+ loggerName = 'Global';
+
+ #observeLogLevels() {
+ if (!this.#logViewerContext) return;
+ this.observe(this.#logViewerContext.loggers, (loggers) => {
+ this._loggers = loggers ?? [];
+ });
+ }
+
+ render() {
+ return html`${this._loggers.length > 0
+ ? this._loggers.find((logger) => logger.name === this.loggerName)?.level
+ : ''}`;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-log-level-overview': UmbLogViewerLogLevelOverviewElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts
new file mode 100644
index 0000000000..1c7ec7a927
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts
@@ -0,0 +1,167 @@
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../../../logviewer.context';
+import { UmbLitElement } from '@umbraco-cms/element';
+import { LogLevelCountsModel } from '@umbraco-cms/backend-api';
+
+@customElement('umb-log-viewer-log-types-chart')
+export class UmbLogViewerLogTypesChartElement extends UmbLitElement {
+ static styles = [
+ css`
+ #log-types-container {
+ display: flex;
+ gap: var(--uui-size-space-4);
+ flex-direction: column-reverse;
+ align-items: center;
+ justify-content: space-between;
+ }
+
+ button {
+ all: unset;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ }
+
+ button:focus {
+ outline: 1px solid var(--uui-color-focus);
+ }
+
+ button.active {
+ text-decoration: line-through;
+ }
+
+ #chart {
+ width: 150px;
+ aspect-ratio: 1;
+ background: radial-gradient(white 40%, transparent 41%),
+ conic-gradient(
+ var(--umb-log-viewer-debug-color) 0% 20%,
+ var(--umb-log-viewer-information-color) 20% 40%,
+ var(--umb-log-viewer-warning-color) 40% 60%,
+ var(--umb-log-viewer-error-color) 60% 80%,
+ var(--umb-log-viewer-fatal-color) 80% 100%
+ );
+ margin: 10px;
+ display: inline-block;
+ border-radius: 50%;
+ }
+
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+ }
+
+ li {
+ display: flex;
+ align-items: center;
+ }
+
+ li uui-icon {
+ margin-right: 1em;
+ }
+ `,
+ ];
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#logViewerContext?.getLogCount();
+ this.#observeStuff();
+ });
+ }
+
+ @state()
+ private _logLevelCount: LogLevelCountsModel | null = null;
+
+ @state()
+ private logLevelCount: [string, number][] = [];
+
+ @state()
+ private _logLevelCountFilter: string[] = [];
+
+ protected willUpdate(_changedProperties: Map): void {
+ if (_changedProperties.has('_logLevelCountFilter')) {
+ this.setLogLevelCount();
+ }
+ }
+
+ #setCountFilter(level: string) {
+ if (this._logLevelCountFilter.includes(level)) {
+ this._logLevelCountFilter = this._logLevelCountFilter.filter((item) => item !== level);
+ return;
+ }
+
+ this._logLevelCountFilter = [...this._logLevelCountFilter, level];
+ }
+
+ setLogLevelCount() {
+ this.logLevelCount = this._logLevelCount
+ ? Object.entries(this._logLevelCount).filter(([level, number]) => !this._logLevelCountFilter.includes(level))
+ : [];
+ }
+
+ #observeStuff() {
+ if (!this.#logViewerContext) return;
+ this.observe(this.#logViewerContext.logCount, (logLevel) => {
+ this._logLevelCount = logLevel ?? null;
+ this.setLogLevelCount();
+ });
+ }
+
+ render() {
+ return html`
+
+
+
+
+ ${this._logLevelCount
+ ? Object.keys(this._logLevelCount).map(
+ (level) =>
+ html`-
+
+
`
+ )
+ : ''}
+
+
+
+ ${this._logLevelCount
+ ? this.logLevelCount.map(
+ ([level, number]) =>
+ html` `
+ )
+ : ''}
+
+
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-log-types-chart': UmbLogViewerLogTypesChartElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-message-templates-overview.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-message-templates-overview.element.ts
new file mode 100644
index 0000000000..5c9cd6f754
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-message-templates-overview.element.ts
@@ -0,0 +1,124 @@
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../../../logviewer.context';
+import { UmbLitElement } from '@umbraco-cms/element';
+import { PagedLogTemplateModel, SavedLogSearchModel } from '@umbraco-cms/backend-api';
+
+//TODO: fix pagination bug when API is fixed
+@customElement('umb-log-viewer-message-templates-overview')
+export class UmbLogViewerMessageTemplatesOverviewElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ #show-more-templates-btn {
+ margin-top: var(--uui-size-space-5);
+ }
+
+ a {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ text-decoration: none;
+ color: inherit;
+ }
+
+ uui-table-cell {
+ padding: 10px 20px;
+ height: unset;
+ }
+
+ uui-table-row {
+ cursor: pointer;
+ }
+
+ uui-table-row:hover > uui-table-cell {
+ background-color: var(--uui-color-surface-alt);
+ }
+ `,
+ ];
+
+ @state()
+ private _messageTemplates: PagedLogTemplateModel | null = null;
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#logViewerContext?.getMessageTemplates(0, 10);
+ this.#observeStuff();
+ });
+ }
+
+ #observeStuff() {
+ if (!this.#logViewerContext) return;
+ this.observe(this.#logViewerContext.messageTemplates, (templates) => {
+ this._messageTemplates = templates ?? null;
+ });
+ }
+
+ #getMessageTemplates() {
+ const take = this._messageTemplates?.items?.length ?? 0;
+ this.#logViewerContext?.getMessageTemplates(0, take + 10);
+ }
+
+ #renderSearchItem = (searchListItem: SavedLogSearchModel) => {
+ return html`
+ {
+ this.#setCurrentQuery(searchListItem.query ?? '');
+ }}
+ label="${searchListItem.name ?? ''}"
+ title="${searchListItem.name ?? ''}"
+ href=${'/section/settings/logviewer/search?lq=' + searchListItem.query}
+ >${searchListItem.name}
+ `;
+ };
+
+ #setCurrentQuery = (query: string) => {
+ this.#logViewerContext?.setFilterExpression(query);
+ };
+
+ render() {
+ return html`
+
+ Total Unique Message types: ${this._messageTemplates?.total}
+
+
+ ${this._messageTemplates
+ ? this._messageTemplates.items.map(
+ (template) =>
+ html`
+ {
+ this.#setCurrentQuery(`@MessageTemplate='${template.messageTemplate}'` ?? '');
+ }}
+ href=${'/section/settings/logviewer/search?lg=@MessageTemplate%3D' + template.messageTemplate}>
+ ${template.messageTemplate} ${template.count}
+
+
+ `
+ )
+ : ''}
+
+
+ Show more
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-message-templates-overview': UmbLogViewerMessageTemplatesOverviewElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-saved-searches-overview.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-saved-searches-overview.element.ts
new file mode 100644
index 0000000000..588e51bd9f
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/components/log-viewer-saved-searches-overview.element.ts
@@ -0,0 +1,101 @@
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../../../logviewer.context';
+import { UmbLitElement } from '@umbraco-cms/element';
+import { SavedLogSearchModel } from '@umbraco-cms/backend-api';
+
+//TODO: implement the saved searches pagination when the API total bug is fixed
+@customElement('umb-log-viewer-saved-searches-overview')
+export class UmbLogViewerSavedSearchesOverviewElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ uui-box {
+ height: 100%;
+ }
+
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+ }
+
+ li {
+ display: flex;
+ align-items: center;
+ }
+
+ li uui-icon {
+ margin-right: 1em;
+ }
+ `,
+ ];
+
+ @state()
+ private _savedSearches: SavedLogSearchModel[] = [];
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#logViewerContext?.getSavedSearches();
+ this.#observeStuff();
+ });
+ }
+
+ #observeStuff() {
+ if (!this.#logViewerContext) return;
+ this.observe(this.#logViewerContext.savedSearches, (savedSearches) => {
+ this._savedSearches = savedSearches ?? [];
+ });
+ }
+
+ #setCurrentQuery(query: string) {
+ this.#logViewerContext?.setFilterExpression(query);
+ }
+
+ #renderSearchItem = (searchListItem: SavedLogSearchModel) => {
+ return html`
+ {
+ this.#setCurrentQuery(searchListItem.query ?? '');
+ }}
+ label="${searchListItem.name ?? ''}"
+ title="${searchListItem.name ?? ''}"
+ href=${'/section/settings/logviewer/search?lq=' + searchListItem.query}
+ >${searchListItem.name}
+ `;
+ };
+
+ render() {
+ return html`
+
+ -
+ {
+ this.#setCurrentQuery('');
+ }}
+ label="All logs"
+ title="All logs"
+ href="/section/settings/logviewer/search"
+ >All logs
+
+ ${this._savedSearches.map(this.#renderSearchItem)}
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-saved-searches-overview': UmbLogViewerSavedSearchesOverviewElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/index.ts
new file mode 100644
index 0000000000..cf7acfb3b9
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/index.ts
@@ -0,0 +1,4 @@
+import './components';
+import { UmbLogViewerOverviewViewElement } from './log-overview-view.element';
+
+export default UmbLogViewerOverviewViewElement;
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/log-overview-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/log-overview-view.element.ts
new file mode 100644
index 0000000000..38191cf274
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/overview/log-overview-view.element.ts
@@ -0,0 +1,159 @@
+import { css, html } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../../logviewer.context';
+import { LogLevelCountsModel } from '@umbraco-cms/backend-api';
+import { UmbLitElement } from '@umbraco-cms/element';
+
+//TODO: add a disabled attribute to the show more button when the total number of items is correctly returned from the endpoint
+@customElement('umb-log-viewer-overview-view')
+export class UmbLogViewerOverviewViewElement extends UmbLitElement {
+ static styles = [
+ css`
+ :host {
+ display: block;
+ }
+
+ #logviewer-layout {
+ margin: 20px;
+ height: calc(100vh - 160px);
+ display: grid;
+ grid-template-columns: 7fr 2fr;
+ grid-template-rows: 1fr 1fr;
+ gap: 20px 20px;
+ grid-auto-flow: row;
+ grid-template-areas:
+ 'saved-searches info'
+ 'common-messages info';
+ }
+
+ #info {
+ grid-area: info;
+ align-self: start;
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ grid-template-rows: repeat(4, 1fr);
+ gap: 20px 20px;
+ }
+
+ #time-period {
+ grid-area: 1 / 1 / 2 / 3;
+ }
+
+ #errors {
+ grid-area: 2 / 1 / 3 / 2;
+ }
+
+ #level {
+ grid-area: 2 / 2 / 3 / 3;
+ }
+
+ #log-lever {
+ color: var(--uui-color-positive);
+ text-align: center;
+ }
+
+ #types {
+ grid-area: 3 / 1 / 5 / 3;
+ }
+
+ #saved-searches-container,
+ to-many-logs-warning {
+ grid-area: saved-searches;
+ }
+
+ #common-messages-container {
+ grid-area: common-messages;
+ --uui-box-default-padding: 0 var(--uui-size-space-5, 18px) var(--uui-size-space-5, 18px)
+ var(--uui-size-space-5, 18px);
+ }
+
+ #common-messages-container > uui-box {
+ height: 100%;
+ }
+
+ uui-label:nth-of-type(2) {
+ display: block;
+ margin-top: var(--uui-size-space-5);
+ }
+
+ #error-count {
+ font-size: 4rem;
+ text-align: center;
+ color: var(--uui-color-danger);
+ }
+ `,
+ ];
+
+ @state()
+ private _errorCount = 0;
+
+ @state()
+ private _logLevelCount: LogLevelCountsModel | null = null;
+
+ @state()
+ private _canShowLogs = false;
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#observeErrorCount();
+ this.#observeCanShowLogs();
+ this.#logViewerContext?.getLogLevels(0, 100);
+ });
+ }
+
+ #observeErrorCount() {
+ if (!this.#logViewerContext) return;
+
+ this.observe(this.#logViewerContext.logCount, () => {
+ this._errorCount = this._logLevelCount?.error ?? 0;
+ });
+ }
+
+ #observeCanShowLogs() {
+ if (!this.#logViewerContext) return;
+ this.observe(this.#logViewerContext.canShowLogs, (canShowLogs) => {
+ this._canShowLogs = canShowLogs ?? false;
+ });
+ }
+
+ render() {
+ return html`
+
+
+
+
+
+
+
+ ${this._errorCount}
+
+
+
+
+
+
+
+
+
+ ${this._canShowLogs
+ ? html`
+
+
+
+
+
+
`
+ : html`
`}
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-overview-view': UmbLogViewerOverviewViewElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/index.ts
new file mode 100644
index 0000000000..2157e5df73
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/index.ts
@@ -0,0 +1,5 @@
+export * from './log-viewer-log-level-filter-menu.element';
+export * from './log-viewer-message.element';
+export * from './log-viewer-messages-list.element';
+export * from './log-viewer-polling-button.element';
+export * from './log-viewer-search-input.element';
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-log-level-filter-menu.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-log-level-filter-menu.element.ts
new file mode 100644
index 0000000000..dc72dcec91
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-log-level-filter-menu.element.ts
@@ -0,0 +1,118 @@
+import { UUICheckboxElement } from '@umbraco-ui/uui';
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html } from 'lit';
+import { customElement, queryAll, state } from 'lit/decorators.js';
+import _ from 'lodash';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../../../logviewer.context';
+import { LogLevelModel } from '@umbraco-cms/backend-api';
+import { UmbLitElement } from '@umbraco-cms/element';
+
+@customElement('umb-log-viewer-log-level-filter-menu')
+export class UmbLogViewerLogLevelFilterMenuElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ #log-level-selector {
+ padding: var(--uui-box-default-padding, var(--uui-size-space-5, 18px));
+ width: 150px;
+ background-color: var(--uui-color-surface);
+ box-shadow: var(--uui-shadow-depth-3);
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-space-3);
+ }
+
+ .log-level-button-indicator {
+ font-weight: 600;
+ }
+
+ .log-level-button-indicator:not(:last-of-type)::after {
+ content: ', ';
+ }
+ `,
+ ];
+
+ @queryAll('#log-level-selector > uui-checkbox')
+ private _logLevelSelectorCheckboxes!: NodeListOf;
+
+ @state()
+ private _logLevelFilter: LogLevelModel[] = [];
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#observeLogLevelFilter();
+ });
+ }
+
+ #observeLogLevelFilter() {
+ if (!this.#logViewerContext) return;
+
+ this.observe(this.#logViewerContext.logLevelsFilter, (levelsFilter) => {
+ this._logLevelFilter = levelsFilter ?? [];
+ });
+ }
+
+ #setLogLevel() {
+ if (!this.#logViewerContext) return;
+ this.#logViewerContext?.setCurrentPage(1);
+
+ const logLevels = Array.from(this._logLevelSelectorCheckboxes)
+ .filter((checkbox) => checkbox.checked)
+ .map((checkbox) => checkbox.value as LogLevelModel);
+ this.#logViewerContext?.setLogLevelsFilter(logLevels);
+ this.#logViewerContext.getLogs();
+ }
+
+ setLogLevelDebounce = _.debounce(this.#setLogLevel, 300);
+
+ #selectAllLogLevels() {
+ this._logLevelSelectorCheckboxes.forEach((checkbox) => (checkbox.checked = true));
+ this.#setLogLevel();
+ }
+
+ #deselectAllLogLevels() {
+ this._logLevelSelectorCheckboxes.forEach((checkbox) => (checkbox.checked = false));
+ this.#setLogLevel();
+ }
+
+ #renderLogLevelSelector() {
+ return html`
+
+ ${Object.values(LogLevelModel).map(
+ (logLevel) =>
+ html``
+ )}
+
+
+
+ `;
+ }
+
+ render() {
+ return html`
+ Log Level:
+ ${this._logLevelFilter.length > 0
+ ? this._logLevelFilter.map((level) => html`${level}`)
+ : 'All'}
+ ${this.#renderLogLevelSelector()}
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-log-level-filter-menu': UmbLogViewerLogLevelFilterMenuElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-message.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-message.element.ts
new file mode 100644
index 0000000000..8bc5edb9c6
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-message.element.ts
@@ -0,0 +1,303 @@
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html, PropertyValueMap } from 'lit';
+import { customElement, property, query, state } from 'lit/decorators.js';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../../../logviewer.context';
+import { LogLevelModel, LogMessagePropertyModel } from '@umbraco-cms/backend-api';
+import { UmbLitElement } from '@umbraco-cms/element';
+
+//TODO: check how to display EventId field in the message properties
+@customElement('umb-log-viewer-message')
+export class UmbLogViewerMessageElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ :host > details {
+ border-top: 1px solid var(--uui-color-border);
+ }
+
+ :host(:last-child) > details {
+ border-bottom: 1px solid var(--uui-color-border);
+ }
+
+ summary {
+ display: flex;
+ }
+
+ details[open] {
+ margin-bottom: var(--uui-size-space-3);
+ }
+
+ summary:hover,
+ #properties-list {
+ background-color: var(--uui-color-background);
+ }
+
+ #properties-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ margin-bottom: var(--uui-size-space-3);
+ }
+
+ .property {
+ padding: 10px 20px;
+ display: flex;
+ border-top: 1px solid var(--uui-color-border);
+ }
+
+ summary > div {
+ box-sizing: border-box;
+ padding: 10px 20px;
+ display: flex;
+ align-items: center;
+ }
+
+ #timestamp {
+ flex: 1 0 14ch;
+ }
+
+ #level,
+ #machine {
+ flex: 1 0 14ch;
+ }
+
+ #message {
+ flex: 6 0 14ch;
+ }
+
+ .property-name,
+ .property-value {
+ display: flex;
+ align-items: center;
+ }
+
+ .property-name {
+ font-weight: 600;
+ flex: 1 1 20ch;
+ }
+
+ .property-value {
+ flex: 3 0 20ch;
+ }
+
+ #search-menu {
+ margin: 0;
+ padding: 0;
+ margin-top: var(--uui-size-space-3);
+ background-color: var(--uui-color-surface);
+ box-shadow: var(--uui-shadow-depth-3);
+ max-width: 25%;
+ }
+
+ #search-menu > li {
+ padding: 0;
+ }
+
+ .search-item {
+ width: 100%;
+ }
+
+ pre {
+ background-color: var(--uui-color-background);
+ border-top: 1px solid #d8d7d9;
+ border-left: 4px solid #d42054;
+ color: #303033;
+ display: block;
+ font-family: Lato, Helvetica Neue, Helvetica, Arial, sans-serif;
+ line-height: 20px;
+ margin: 0;
+ overflow-x: auto;
+ padding: 9.5px;
+ white-space: pre-wrap;
+ }
+ `,
+ ];
+
+ @query('details')
+ details!: HTMLDetailsElement;
+
+ @property()
+ timestamp = '';
+
+ @state()
+ date?: Date;
+
+ @property()
+ level: LogLevelModel | '' = '';
+
+ @property()
+ messageTemplate = '';
+
+ @property()
+ renderedMessage = '';
+
+ @property({ attribute: false })
+ properties: Array = [];
+
+ @property({ type: Boolean })
+ open = false;
+
+ @property()
+ exception = '';
+
+ willUpdate(changedProperties: Map) {
+ if (changedProperties.has('timestamp')) {
+ this.date = new Date(this.timestamp);
+ }
+ }
+
+ protected updated(_changedProperties: PropertyValueMap | Map): void {
+ if (_changedProperties.has('open')) {
+ this.open ? this.details.setAttribute('open', 'true') : this.details.removeAttribute('open');
+ }
+ }
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ });
+ }
+
+ private _searchMenuData: Array<{ label: string; href: () => string; icon: string; title: string }> = [
+ {
+ label: 'Search in Google',
+ title: '@logViewer_searchThisMessageWithGoogle',
+ href: () => `https://www.google.com/search?q=${this.renderedMessage}`,
+ icon: 'https://www.google.com/favicon.ico',
+ },
+ {
+ label: 'Search in Bing',
+ title: 'Search this message with Bing',
+ href: () => `https://www.bing.com/search?q=${this.renderedMessage}`,
+ icon: 'https://www.bing.com/favicon.ico',
+ },
+ {
+ label: 'Search in OurUmbraco',
+ title: 'Search this message on Our Umbraco forums and docs',
+ href: () => `https://our.umbraco.com/search?q=${this.renderedMessage}&content=wiki,forum,documentation`,
+ icon: 'https://our.umbraco.com/assets/images/app-icons/favicon.png',
+ },
+ {
+ label: 'Search in OurUmbraco with Google',
+ title: 'Search Our Umbraco forums using Google',
+ href: () =>
+ `https://www.google.co.uk/?q=site:our.umbraco.com ${this.renderedMessage}&safe=off#q=site:our.umbraco.com ${
+ this.renderedMessage
+ } ${this.properties.find((property) => property.name === 'SourceContext')?.value}&safe=off"`,
+ icon: 'https://www.google.com/favicon.ico',
+ },
+ {
+ label: 'Search Umbraco Source',
+ title: 'Search within Umbraco source code on Github',
+ href: () =>
+ `https://github.com/umbraco/Umbraco-CMS/search?q=${
+ this.properties.find((property) => property.name === 'SourceContext')?.value
+ }`,
+ icon: 'https://github.githubassets.com/favicon.ico',
+ },
+ {
+ label: 'Search Umbraco Issues',
+ title: 'Search Umbraco Issues on Github',
+ href: () =>
+ `https://github.com/umbraco/Umbraco-CMS/issues?q=${
+ this.properties.find((property) => property.name === 'SourceContext')?.value
+ }`,
+ icon: 'https://github.githubassets.com/favicon.ico',
+ },
+ ];
+
+ private _propertiesWithSearchMenu: Array = ['HttpRequestNumber', 'SourceContext', 'MachineName'];
+
+ private _findLogsWithProperty({ name, value }: LogMessagePropertyModel) {
+ let queryString = '';
+
+ if (isNaN(+(value ?? ''))) {
+ queryString = name + "='" + value + "'";
+ } else {
+ queryString = name + '=' + value;
+ }
+
+ this.#logViewerContext?.setFilterExpression(queryString);
+ this.#logViewerContext?.setCurrentPage(1);
+ this.details.removeAttribute('open');
+ this.#logViewerContext?.getLogs();
+ }
+
+ #setOpen(event: Event) {
+ this.open = (event.target as HTMLDetailsElement).open;
+ }
+
+ render() {
+ return html`
+
+
+ ${this.date?.toLocaleString()}
+
+
+
+ ${this.properties.find((property) => property.name === 'MachineName')?.value}
+ ${this.renderedMessage}
+
+ ${this.exception ? html`${this.exception}` : ''}
+
+ -
+
Timestamp
+ ${this.date?.toLocaleString()}
+
+ -
+
@MessageTemplate
+ ${this.messageTemplate}
+
+ ${this.properties.map(
+ (property) =>
+ html`-
+
${property.name}:
+
+ ${property.value}
+ ${this._propertiesWithSearchMenu.includes(property.name ?? '')
+ ? html` {
+ this._findLogsWithProperty(property);
+ }}
+ look="secondary"
+ label="Find logs with ${property.name}"
+ title="Find logs with ${property.name}"
+ >`
+ : ''}
+
+ `
+ )}
+
+
+ Search
+
+
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-message': UmbLogViewerMessageElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-messages-list.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-messages-list.element.ts
new file mode 100644
index 0000000000..1fc81d55fa
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-messages-list.element.ts
@@ -0,0 +1,158 @@
+import { UUIScrollContainerElement, UUIPaginationElement } from '@umbraco-ui/uui';
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html } from 'lit';
+import { customElement, query, state } from 'lit/decorators.js';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../../../logviewer.context';
+import { UmbLitElement } from '@umbraco-cms/element';
+import { DirectionModel, LogMessageModel } from '@umbraco-cms/backend-api';
+
+@customElement('umb-log-viewer-messages-list')
+export class UmbLogViewerMessagesListElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ #message-list-header {
+ display: flex;
+ font-weight: 600;
+ }
+
+ #message-list-header > div {
+ box-sizing: border-box;
+ padding: 10px 20px;
+ display: flex;
+ align-items: center;
+ }
+
+ #timestamp {
+ flex: 1 0 14ch;
+ }
+
+ #level,
+ #machine {
+ flex: 1 0 14ch;
+ }
+
+ #message {
+ flex: 6 0 14ch;
+ }
+
+ #empty {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: var(--uui-size-space-3);
+ }
+
+ #pagination {
+ margin: var(--uui-size-space-5, 18px) 0;
+ }
+ `,
+ ];
+
+ @query('#logs-scroll-container')
+ private _logsScrollContainer!: UUIScrollContainerElement;
+
+ @state()
+ private _sortingDirection: DirectionModel = DirectionModel.ASCENDING;
+
+ @state()
+ private _logs: LogMessageModel[] = [];
+
+ @state()
+ private _logsTotal = 0;
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#observeLogs();
+ this.#logViewerContext.getLogs();
+ });
+ }
+
+ #observeLogs() {
+ if (!this.#logViewerContext) return;
+
+ this.observe(this.#logViewerContext.logs, (logs) => {
+ this._logs = logs ?? [];
+ });
+
+ this.observe(this.#logViewerContext.logsTotal, (total) => {
+ this._logsTotal = total ?? 0;
+ });
+
+ this.observe(this.#logViewerContext.sortingDirection, (direction) => {
+ this._sortingDirection = direction;
+ });
+ }
+
+ #sortLogs() {
+ this.#logViewerContext?.toggleSortOrder();
+ this.#logViewerContext?.setCurrentPage(1);
+ this.#logViewerContext?.getLogs();
+ }
+
+ _onPageChange(event: Event): void {
+ const current = (event.target as UUIPaginationElement).current;
+ this.#logViewerContext?.setCurrentPage(current);
+ this.#logViewerContext?.getLogs();
+ this._logsScrollContainer.scrollTop = 0;
+ }
+
+ private _renderPagination() {
+ if (!this._logsTotal) return '';
+
+ const totalPages = Math.ceil(this._logsTotal / 100);
+
+ if (totalPages <= 1) return '';
+
+ return html``;
+ }
+
+ render() {
+ return html`
+ Total items: ${this._logsTotal}
+
+
+ ${this._logs.length > 0
+ ? html` ${this._logs.map(
+ (log) => html``
+ )}`
+ : html`
+ Sorry, we cannot find what you are looking for.
+ `}
+
+ ${this._renderPagination()}
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-messages-list': UmbLogViewerMessagesListElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-polling-button.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-polling-button.element.ts
new file mode 100644
index 0000000000..5ee674f1d4
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-polling-button.element.ts
@@ -0,0 +1,138 @@
+import { UUIPopoverElement, UUISymbolExpandElement } from '@umbraco-ui/uui';
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html } from 'lit';
+import { customElement, query, state } from 'lit/decorators.js';
+import {
+ PoolingCOnfig,
+ PoolingInterval,
+ UmbLogViewerWorkspaceContext,
+ UMB_APP_LOG_VIEWER_CONTEXT_TOKEN,
+} from '../../../logviewer.context';
+import { UmbLitElement } from '@umbraco-cms/element';
+
+@customElement('umb-log-viewer-polling-button')
+export class UmbLogViewerPollingButtonElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ #polling-interval-menu {
+ margin: 0;
+ padding: 0;
+ width: 20ch;
+ background-color: var(--uui-color-surface);
+ box-shadow: var(--uui-shadow-depth-3);
+ display: flex;
+ flex-direction: column;
+ transform: translateX(calc((100% - 33px) * -1));
+ }
+
+ #polling-enabled-icon {
+ margin-right: var(--uui-size-space-3);
+ margin-bottom: 1px;
+ -webkit-animation: rotate-center 0.8s ease-in-out infinite both;
+ animation: rotate-center 0.8s ease-in-out infinite both;
+ }
+
+ @-webkit-keyframes rotate-center {
+ 0% {
+ -webkit-transform: rotate(0);
+ transform: rotate(0);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+ }
+ @keyframes rotate-center {
+ 0% {
+ -webkit-transform: rotate(0);
+ transform: rotate(0);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+ }
+ `,
+ ];
+
+ @query('#polling-popover')
+ private _pollingPopover!: UUIPopoverElement;
+
+ @query('#polling-expand-symbol')
+ private _polingExpandSymbol!: UUISymbolExpandElement;
+
+ @state()
+ private _poolingConfig: PoolingCOnfig = { enabled: false, interval: 0 };
+
+ #pollingIntervals: PoolingInterval[] = [2000, 5000, 10000, 20000, 30000];
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#observePoolingConfig();
+ this.#logViewerContext.getLogs();
+ });
+ }
+
+ #observePoolingConfig() {
+ if (!this.#logViewerContext) return;
+
+ this.observe(this.#logViewerContext.polling, (poolingConfig) => {
+ this._poolingConfig = { ...poolingConfig };
+ });
+ }
+
+ #togglePolling() {
+ this.#logViewerContext?.togglePolling();
+ }
+
+ #setPolingInterval(interval: PoolingInterval) {
+ this.#logViewerContext?.setPollingInterval(interval);
+ this.#closePoolingPopover();
+ }
+
+ #openPoolingPopover() {
+ this._pollingPopover.open = true;
+ this._polingExpandSymbol.open = true;
+ }
+
+ #closePoolingPopover() {
+ this._pollingPopover.open = false;
+ this._polingExpandSymbol.open = false;
+ }
+
+ render() {
+ return html`
+ ${this._poolingConfig.enabled
+ ? html`Polling
+ ${this._poolingConfig.interval / 1000} seconds`
+ : 'Pooling'}
+ (this._polingExpandSymbol.open = false)}>
+
+
+
+
+
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-polling-button': UmbLogViewerPollingButtonElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-search-input.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-search-input.element.ts
new file mode 100644
index 0000000000..c4ca1a8ffa
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/components/log-viewer-search-input.element.ts
@@ -0,0 +1,211 @@
+import { UUIInputElement, UUIPopoverElement, UUISymbolExpandElement } from '@umbraco-ui/uui';
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html } from 'lit';
+import { customElement, query, state } from 'lit/decorators.js';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../../../logviewer.context';
+import { SavedLogSearchModel } from '@umbraco-cms/backend-api';
+import { UmbLitElement } from '@umbraco-cms/element';
+
+@customElement('umb-log-viewer-search-input')
+export class UmbLogViewerSearchInputElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ :host {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--uui-size-space-4);
+ }
+
+ #search-input {
+ width: 100%;
+ }
+
+ #saved-searches-button {
+ flex-shrink: 0;
+ }
+
+ #saved-searches-popover {
+ flex: 1;
+ }
+
+ #saved-searches-container {
+ width: 100%;
+ max-height: 300px;
+ background-color: var(--uui-color-surface);
+ box-shadow: var(--uui-shadow-depth-1);
+ }
+
+ .saved-search-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: stretch;
+ border-bottom: 1px solid #e9e9eb;
+ }
+
+ .saved-search-item-button {
+ display: flex;
+ font-family: inherit;
+ flex: 1;
+ background: 0 0;
+ padding: 0 0;
+ border: 0;
+ clear: both;
+ cursor: pointer;
+ display: flex;
+ font-weight: 400;
+ line-height: 20px;
+ text-align: left;
+ align-items: center;
+ white-space: nowrap;
+ color: var(--uui-color-interactive);
+ }
+
+ .saved-search-item-button:hover {
+ background-color: var(--uui-color-surface-emphasis, rgb(250, 250, 250));
+ color: var(--color-standalone);
+ }
+
+ .saved-search-item-name {
+ font-weight: 600;
+ margin: 0 var(--uui-size-space-3);
+ }
+
+ #polling-symbol-expand,
+ #saved-search-expand-symbol,
+ uui-symbol-sort {
+ margin-left: var(--uui-size-space-3);
+ }
+ `,
+ ];
+
+ @query('#saved-searches-popover')
+ private _savedSearchesPopover!: UUIPopoverElement;
+
+ @query('#saved-search-expand-symbol')
+ private _savedSearchesExpandSymbol!: UUISymbolExpandElement;
+
+ @state()
+ private _savedSearches: SavedLogSearchModel[] = [];
+
+ @state()
+ private _inputQuery = '';
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#observeStuff();
+ this.#logViewerContext.getLogs();
+ });
+ }
+
+ #observeStuff() {
+ if (!this.#logViewerContext) return;
+ this.observe(this.#logViewerContext.savedSearches, (savedSearches) => {
+ this._savedSearches = savedSearches ?? [];
+ });
+
+ this.observe(this.#logViewerContext.filterExpression, (query) => {
+ this._inputQuery = query;
+ });
+ }
+
+ #toggleSavedSearchesPopover() {
+ this._savedSearchesPopover.open = !this._savedSearchesPopover.open;
+ }
+
+ #toggleSavedSearchesExpandSymbol() {
+ this._savedSearchesExpandSymbol.open = !this._savedSearchesExpandSymbol.open;
+ }
+
+ #openSavedSearchesPopover() {
+ this.#toggleSavedSearchesPopover();
+ this.#toggleSavedSearchesExpandSymbol();
+ }
+
+ #setQuery(event: Event) {
+ const target = event.target as UUIInputElement;
+ this._inputQuery = target.value as string;
+ this.#logViewerContext?.setFilterExpression(this._inputQuery);
+ }
+
+ #setQueryFromSavedSearch(query: string) {
+ this._inputQuery = query;
+ this.#logViewerContext?.setFilterExpression(query);
+ this.#logViewerContext?.setCurrentPage(1);
+
+ this.#logViewerContext?.getLogs();
+ this._savedSearchesPopover.open = false;
+ }
+
+ #clearQuery() {
+ this._inputQuery = '';
+ this.#logViewerContext?.setFilterExpression('');
+ this.#logViewerContext?.getLogs();
+ }
+
+ #search() {
+ this.#logViewerContext?.setCurrentPage(1);
+
+ this.#logViewerContext?.getLogs();
+ }
+
+ render() {
+ return html`
+
+ ${this._inputQuery
+ ? html``
+ : html``}
+ Saved searches
+
+
+
+ ${this._savedSearches.map(
+ (search) =>
+ html`
+
+ `
+ )}
+
+
+ Search`;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-search-input': UmbLogViewerSearchInputElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/index.ts
new file mode 100644
index 0000000000..4760a61f5a
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/index.ts
@@ -0,0 +1,4 @@
+import './components';
+import { UmbLogViewerSearchViewElement } from './log-search-view.element';
+
+export default UmbLogViewerSearchViewElement;
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/log-search-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/log-search-view.element.ts
new file mode 100644
index 0000000000..c9813ae53c
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/logviewer/workspace/views/search/log-search-view.element.ts
@@ -0,0 +1,87 @@
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+import { UmbLogViewerWorkspaceContext, UMB_APP_LOG_VIEWER_CONTEXT_TOKEN } from '../../logviewer.context';
+import { UmbLitElement } from '@umbraco-cms/element';
+
+@customElement('umb-log-viewer-search-view')
+export class UmbLogViewerSearchViewElement extends UmbLitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ #layout {
+ margin: 20px;
+ }
+ #levels-container,
+ #input-container {
+ display: flex;
+ align-items: center;
+ gap: var(--uui-size-space-4);
+ width: 100%;
+ margin-bottom: 20px;
+ }
+
+ #levels-container {
+ justify-content: space-between;
+ }
+
+ #dates-polling-container {
+ display: flex;
+ align-items: baseline;
+ }
+
+ umb-log-viewer-search-input {
+ flex: 1;
+ }
+
+ umb-log-viewer-date-range-selector {
+ flex-direction: row;
+ }
+ `,
+ ];
+
+ @state()
+ private _canShowLogs = false;
+
+ #logViewerContext?: UmbLogViewerWorkspaceContext;
+ constructor() {
+ super();
+ this.consumeContext(UMB_APP_LOG_VIEWER_CONTEXT_TOKEN, (instance) => {
+ this.#logViewerContext = instance;
+ this.#observeCanShowLogs();
+ });
+ }
+
+ #observeCanShowLogs() {
+ if (!this.#logViewerContext) return;
+ this.observe(this.#logViewerContext.canShowLogs, (canShowLogs) => {
+ this._canShowLogs = canShowLogs ?? false;
+ });
+ }
+
+ render() {
+ return html`
+
+
+
+
+
+ ${this._canShowLogs
+ ? html`
`
+ : html`
`}
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-log-viewer-search-view': UmbLogViewerSearchViewElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-modal-container.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-modal-container.element.ts
index 1ad3d99bf3..dd257e3841 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-modal-container.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/backoffice-frame/backoffice-modal-container.element.ts
@@ -2,7 +2,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, CSSResultGroup, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
-import { UmbModalHandler, UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '../../../../core/modal';
+import { UmbModalHandler, UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
import { UmbLitElement } from '@umbraco-cms/element';
@customElement('umb-backoffice-modal-container')
@@ -41,7 +41,7 @@ export class UmbBackofficeModalContainer extends UmbLitElement {
render() {
return html`
- ${this._modals ? repeat(this._modals, (modalHandler) => html`${modalHandler.element}`) : ''}
+ ${this._modals ? repeat(this._modals, (modalHandler) => html`${modalHandler.modalElement}`) : ''}
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.element.ts
index 0770f98a60..66406ae01c 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.element.ts
@@ -2,6 +2,18 @@ import { css, html, LitElement, nothing } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property, state } from 'lit/decorators.js';
+/**
+ * @element umb-body-layout
+ * @description Layout element to arrange elements in a body layout. A general layout for most views.
+ * @slot icon - Slot for icon
+ * @slot name - Slot for name
+ * @slot footer - Slot for workspace footer
+ * @slot actions - Slot for workspace footer actions
+ * @slot default - slot for main content
+ * @export
+ * @class UmbBodyLayout
+ * @extends {UmbLitElement}
+ */
@customElement('umb-body-layout')
export class UmbBodyLayout extends LitElement {
static styles = [
@@ -40,24 +52,6 @@ export class UmbBodyLayout extends LitElement {
flex: 1;
flex-direction: column;
}
-
- #footer {
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- height: 54px; /* TODO: missing var(--uui-size-18);*/
- border-top: 1px solid var(--uui-color-border);
- background-color: var(--uui-color-surface);
- box-sizing: border-box;
- }
-
- #actions {
- display: flex;
- gap: var(--uui-size-space-2);
- margin: 0 var(--uui-size-layout-1);
- margin-left: auto;
- }
`,
];
@@ -77,15 +71,15 @@ export class UmbBodyLayout extends LitElement {
@state()
private _tabsSlotHasChildren = false;
+ @state()
+ private _actionsMenuSlotHasChildren = false;
+
@state()
private _footerSlotHasChildren = false;
@state()
private _actionsSlotHasChildren = false;
- @state()
- private _actionsMenuSlotHasChildren = false;
-
#hasNodes = (e: Event) => {
return (e.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0;
};
@@ -123,20 +117,19 @@ export class UmbBodyLayout extends LitElement {
-
+
`;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.stories.ts
index b26242bee7..087ae4c959 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.stories.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.stories.ts
@@ -1,19 +1,22 @@
-import './body-layout.element';
-
-import { Meta, Story } from '@storybook/web-components';
+import { Meta, StoryObj } from '@storybook/web-components';
import { html } from 'lit-html';
+import './body-layout.element';
import type { UmbBodyLayout } from './body-layout.element';
-export default {
- title: 'Workspaces/Shared/Workspace Layout',
- component: 'umb-body-layout',
- id: 'umb-body-layout',
-} as Meta;
+const meta: Meta = {
+ title: 'Components/Workspace Layout',
+ component: 'umb-body-layout'
+};
+
+export default meta;
+type Story = StoryObj;
-export const AAAOverview: Story = () => html`
- Header slot
- Main slot
- Footer slot
-`;
-AAAOverview.storyName = 'Overview';
+export const Overview: Story = {
+ render: () => html`
+
+ Header slot
+ Main slot
+ Footer slot
+ `
+};
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/button-with-dropdown/button-with-dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/button-with-dropdown/button-with-dropdown.element.ts
new file mode 100644
index 0000000000..b9aeb923cd
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/button-with-dropdown/button-with-dropdown.element.ts
@@ -0,0 +1,82 @@
+import { UUITextStyles } from '@umbraco-ui/uui-css';
+import { css, html, LitElement } from 'lit';
+import { customElement, property, query } from 'lit/decorators.js';
+import { PopoverPlacement, UUIPopoverElement, UUISymbolExpandElement } from '@umbraco-ui/uui';
+import { InterfaceColor, InterfaceLook } from '@umbraco-ui/uui-base/lib/types';
+
+// TODO: maybe this should go to UI library? It's a common pattern
+// TODO: consider not using this, but instead use dropdown, which is more generic shared component of backoffice. (this is at the movement only used in Log Viewer)
+@customElement('umb-button-with-dropdown')
+export class UmbButtonWithDropdownElement extends LitElement {
+ static styles = [
+ UUITextStyles,
+ css`
+ uui-symbol-expand {
+ margin-left: var(--uui-size-space-3);
+ }
+ `,
+ ];
+
+ @property()
+ label = '';
+
+ @property()
+ open = false;
+
+ @property()
+ look: InterfaceLook = 'default';
+
+ @property()
+ color: InterfaceColor = 'default';
+
+ @property()
+ placement: PopoverPlacement = 'bottom-start';
+
+ @query('#symbol-expand')
+ symbolExpand!: UUISymbolExpandElement;
+
+ @query('#popover')
+ popover!: UUIPopoverElement;
+
+ #openPopover() {
+ this.open = true;
+ this.popover.open = true;
+ this.symbolExpand.open = true;
+ }
+
+ #closePopover() {
+ this.open = false;
+ this.popover.open = false;
+ this.symbolExpand.open = false;
+ }
+
+ #togglePopover() {
+ this.open ? this.#closePopover() : this.#openPopover();
+ }
+
+ render() {
+ return html`
+
+
+
+
+
+
+
+
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-button-with-dropdown': UmbButtonWithDropdownElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/button-with-dropdown/button-with-dropdown.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/button-with-dropdown/button-with-dropdown.stories.ts
new file mode 100644
index 0000000000..d42a021c21
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/button-with-dropdown/button-with-dropdown.stories.ts
@@ -0,0 +1,16 @@
+
+import { Meta, Story } from '@storybook/web-components';
+import { html } from 'lit-html';
+import { UmbButtonWithDropdownElement } from './button-with-dropdown.element';
+
+export default {
+ title: 'Components/Button with dropdown',
+ component: 'umb-button-with-dropdown',
+ id: 'umb-button-with-dropdown',
+} as Meta;
+
+export const AAAOverview: Story = () => html`
+ Open me
+ I am a dropdown
+`;
+AAAOverview.storyName = 'Overview';
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/code-block/code-block.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/code-block/code-block.stories.ts
new file mode 100644
index 0000000000..533a46b28e
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/code-block/code-block.stories.ts
@@ -0,0 +1,26 @@
+import { Meta, StoryObj } from '@storybook/web-components';
+import { html } from 'lit-html';
+import './code-block.element';
+import type { UUICodeBlock } from './code-block.element';
+
+const meta: Meta = {
+ title: 'Components/Code Block',
+ component: 'uui-code-block'
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Overview: Story = {
+ args: {
+ }
+};
+
+export const WithCode: Story = {
+ decorators: [],
+ render: () => html`
+
+ // Lets write some javascript
+ alert("Hello World");
+ `
+};
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts
index ae75471fe0..f790057ecf 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts
@@ -1,6 +1,7 @@
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, nothing, TemplateResult } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
+import { UMB_CONTEXT_DEBUGGER_MODAL_TOKEN } from './modals/debug';
import { UmbContextDebugRequest } from '@umbraco-cms/context-api';
import { UmbLitElement } from '@umbraco-cms/element';
import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
@@ -102,10 +103,8 @@ export class UmbDebug extends UmbLitElement {
}
private _openDialog() {
- this._modalContext?.openBasic({
- header: html` Debug: Contexts`,
- content: this._htmlContent(),
- overlaySize: 'small',
+ this._modalContext?.open(UMB_CONTEXT_DEBUGGER_MODAL_TOKEN, {
+ content: html`${this._renderContextAliases()}`,
});
}
@@ -125,19 +124,15 @@ export class UmbDebug extends UmbLitElement {
-
${this._htmlContent()}
+
+
+ ${this._renderContextAliases()}
+
+
`;
}
- private _htmlContent() {
- return html`
-