From 3d657f13f6052563382f3630d2c978bb6760bdbf Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 13:59:18 +0100 Subject: [PATCH 01/98] move user utils into user module --- .../packages/user/{ => user}/utils.test.ts | 0 .../src/packages/user/user/utils.ts | 24 +++++++++++++++++++ .../src/packages/user/utils.ts | 24 ------------------- 3 files changed, 24 insertions(+), 24 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/user/{ => user}/utils.test.ts (100%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/user/utils.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/utils.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/utils.test.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/utils.test.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/user/utils.test.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/user/utils.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/utils.ts new file mode 100644 index 0000000000..15f279757d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/utils.ts @@ -0,0 +1,24 @@ +import { InterfaceColor, InterfaceLook } from '@umbraco-cms/backoffice/external/uui'; +import { UserStateModel } from '@umbraco-cms/backoffice/backend-api'; + +export interface UmbUserDisplayStatus { + look: InterfaceLook; + color: InterfaceColor; + key: string; +} +const userStates: UmbUserDisplayStatus[] = [ + { key: 'All', color: 'positive', look: 'secondary' }, + { key: 'Active', color: 'positive', look: 'primary' }, + { key: 'Disabled', color: 'danger', look: 'primary' }, + { key: 'LockedOut', color: 'danger', look: 'secondary' }, + { key: 'Invited', color: 'warning', look: 'primary' }, + { key: 'Inactive', color: 'warning', look: 'primary' }, +]; + +export const getDisplayStateFromUserStatus = (status?: UserStateModel): UmbUserDisplayStatus => + userStates + .filter((state) => state.key === status)! + .map((state) => ({ + ...state, + key: 'state' + state.key, + }))[0]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/user/utils.ts deleted file mode 100644 index d1ae7b30f3..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/user/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { InterfaceColor, InterfaceLook } from '@umbraco-cms/backoffice/external/uui'; -import { UserStateModel } from '@umbraco-cms/backoffice/backend-api'; - -interface DisplayStatus { - look: InterfaceLook; - color: InterfaceColor; - key: string; -} -const userStates: DisplayStatus[] = [ - { "key": "All", color:"positive", look: "secondary" } , - { "key": "Active", "color": "positive", look: "primary" }, - { "key": "Disabled", "color": "danger", look: "primary" }, - { "key": "LockedOut", "color": "danger", look: "secondary" }, - { "key": "Invited", "color": "warning", look: "primary" }, - { "key": "Inactive", "color": "warning", look: "primary" } -]; - -export const getDisplayStateFromUserStatus = (status?: UserStateModel): DisplayStatus => - userStates - .filter(state => state.key === status)! - .map(state => ({ - ...state, - key:'state'+state.key - }))[0] From 75d1640834a2791d54942dea494cc138d544ace2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 13:59:52 +0100 Subject: [PATCH 02/98] update imports --- .../collection/views/grid/user-grid-collection-view.element.ts | 2 +- .../status/user-table-status-column-layout.element.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts index d1e320feec..50bf9d2eb4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts @@ -1,4 +1,4 @@ -import { getDisplayStateFromUserStatus } from '../../../../utils.js'; +import { getDisplayStateFromUserStatus } from '../../../utils.js'; import { UmbUserCollectionContext } from '../../user-collection.context.js'; import { type UmbUserDetailModel } from '../../../types.js'; import { css, html, nothing, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/column-layouts/status/user-table-status-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/column-layouts/status/user-table-status-column-layout.element.ts index c8d4bbf3f1..19880ac087 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/column-layouts/status/user-table-status-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/column-layouts/status/user-table-status-column-layout.element.ts @@ -1,4 +1,4 @@ -import { getDisplayStateFromUserStatus } from '../../../../../../utils.js'; +import { getDisplayStateFromUserStatus } from '../../../../../utils.js'; import { html, LitElement, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit'; @customElement('umb-user-table-status-column-layout') From 09ab15035ebc0f6531a09b567e74a3aeb919a90b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 14:00:18 +0100 Subject: [PATCH 03/98] remove avatar logic from user info element --- .../user-workspace-info.element.ts | 165 +++--------------- .../user-workspace-editor.element.ts | 2 +- 2 files changed, 28 insertions(+), 139 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-info/user-workspace-info.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-info/user-workspace-info.element.ts index c10b796dbb..25751398d4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-info/user-workspace-info.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-info/user-workspace-info.element.ts @@ -1,35 +1,19 @@ -import { getDisplayStateFromUserStatus } from '../../../../utils.js'; +import { UmbUserDisplayStatus, getDisplayStateFromUserStatus } from '../../../utils.js'; import { UMB_USER_WORKSPACE_CONTEXT } from '../../user-workspace.context.js'; import { UmbUserDetailModel } from '../../../types.js'; -import { - html, - customElement, - state, - css, - repeat, - ifDefined, - query, - nothing, -} from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, state, css, repeat, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; type UmbUserWorkspaceInfoItem = { labelKey: string; value: string | number | undefined }; @customElement('umb-user-workspace-info') export class UmbUserWorkspaceInfoElement extends UmbLitElement { - @state() - private _user?: UmbUserDetailModel; - - @state() - private _userAvatarUrls: Array<{ url: string; scale: string }> = []; - @state() private _userInfo: Array = []; - @query('#AvatarFileField') - _avatarFileField?: HTMLInputElement; + @state() + private _userDisplayState: UmbUserDisplayStatus | null = null; #userWorkspaceContext?: typeof UMB_USER_WORKSPACE_CONTEXT.TYPE; @@ -41,63 +25,15 @@ export class UmbUserWorkspaceInfoElement extends UmbLitElement { this.observe( this.#userWorkspaceContext.data, async (user) => { - this._user = user; - this.#setUserAvatarUrls(user); + if (!user) return; this.#setUserInfoItems(user); + this._userDisplayState = getDisplayStateFromUserStatus(user.state); }, 'umbUserObserver', ); }); } - async #getAppContext() { - // TODO: remove this when we get absolute urls from the server - return this.consumeContext(UMB_APP_CONTEXT, (instance) => {}).asPromise(); - } - - // TODO: remove this when we get absolute urls from the server - #setUserAvatarUrls = async (user: UmbUserDetailModel | undefined) => { - if (user?.avatarUrls?.length === 0) return; - - const serverUrl = (await this.#getAppContext()).getServerUrl(); - if (!serverUrl) return; - - this._userAvatarUrls = [ - { - scale: '1x', - url: `${serverUrl}${user?.avatarUrls?.[3]}`, - }, - { - scale: '2x', - url: `${serverUrl}${user?.avatarUrls?.[4]}`, - }, - ]; - }; - - #onAvatarUploadSubmit = (event: SubmitEvent) => { - event.preventDefault(); - - const form = event.target as HTMLFormElement; - if (!form) return; - - if (!form.checkValidity()) return; - - const formData = new FormData(form); - - const avatarFile = formData.get('avatarFile') as File; - - this.#userWorkspaceContext?.uploadAvatar(avatarFile); - }; - - #deleteAvatar = async () => { - if (!this.#userWorkspaceContext) return; - const { error } = await this.#userWorkspaceContext.deleteAvatar(); - - if (!error) { - this._userAvatarUrls = []; - } - }; - #setUserInfoItems = (user: UmbUserDetailModel | undefined) => { if (!user) { this._userInfo = []; @@ -131,67 +67,27 @@ export class UmbUserWorkspaceInfoElement extends UmbLitElement { }; render() { - if (!this._user) return html`User not found`; - - const displayState = getDisplayStateFromUserStatus(this._user.state); + if (this._userInfo.length === 0) return nothing; + return html`${this.#renderState()} ${this.#renderInfoList()} `; + } + #renderState() { return html` - ${this.#renderAvatar()} - - - - - ${repeat( - this._userInfo, - (item) => item.labelKey, - (item) => this.#renderInfoItem(item.labelKey, item.value), - )} - + `; } - #getAvatarSrcset() { - let string = ''; - - this._userAvatarUrls?.forEach((url) => { - string += `${url.url} ${url.scale},`; - }); - return string; - } - - #hasAvatar() { - return this._userAvatarUrls.length > 0; - } - - #renderAvatar() { + #renderInfoList() { return html` - - - + ${repeat( + this._userInfo, + (item) => item.labelKey, + (item) => this.#renderInfoItem(item.labelKey, item.value), + )} `; } @@ -211,27 +107,20 @@ export class UmbUserWorkspaceInfoElement extends UmbLitElement { width: fit-content; } - #Avatar { - font-size: 75px; - place-self: center; - } - #user-info { margin-bottom: var(--uui-size-space-4); } - #user-info > .user-info-item { + #state { + border-bottom: 1px solid var(--uui-color-divider); + padding-bottom: var(--uui-size-space-4); + } + + .user-info-item { display: flex; flex-direction: column; margin-bottom: var(--uui-size-space-3); } - - #user-avatar-settings form { - text-align: center; - display: flex; - flex-direction: column; - gap: var(--uui-size-space-2); - } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace-editor.element.ts index 217d59907e..dcdeccf91d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace-editor.element.ts @@ -7,7 +7,7 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -// Import of local components that should only be used here +// import local components. Theses are not meant to be used outside of this component. import './components/user-workspace-profile-settings/user-workspace-profile-settings.element.js'; import './components/user-workspace-access-settings/user-workspace-access-settings.element.js'; import './components/user-workspace-info/user-workspace-info.element.js'; From fb8eacc6d1e8cb90f4e073b30155741517740304 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 14:41:09 +0100 Subject: [PATCH 04/98] add element for user workspace avatar --- .../user-workspace-avatar.element.ts | 161 ++++++++++++++++++ .../user-workspace-editor.element.ts | 2 + 2 files changed, 163 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts new file mode 100644 index 0000000000..db6d57970f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts @@ -0,0 +1,161 @@ +import { UmbUserDetailModel } from '../../../types.js'; +import { UMB_USER_WORKSPACE_CONTEXT } from '../../user-workspace.context.js'; +import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; +import { css, html, customElement, query, nothing, ifDefined, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; + +@customElement('umb-user-workspace-avatar') +export class UmbUserAvatarElement extends UmbLitElement { + @state() + private _user?: UmbUserDetailModel; + + @state() + private _userAvatarUrls: Array<{ url: string; scale: string }> = []; + + @query('#AvatarFileField') + _avatarFileField?: HTMLInputElement; + + @query('uui-combobox') + private _selectElement!: HTMLInputElement; + + #userWorkspaceContext?: typeof UMB_USER_WORKSPACE_CONTEXT.TYPE; + + constructor() { + super(); + + this.consumeContext(UMB_USER_WORKSPACE_CONTEXT, (instance) => { + this.#userWorkspaceContext = instance; + if (!this.#userWorkspaceContext) return; + this.#observeUser(); + }); + } + + protected getFormElement() { + return this._selectElement; + } + + #observeUser = () => { + this.observe( + this.#userWorkspaceContext!.data, + async (user) => { + this._user = user; + this.#setUserAvatarUrls(user); + }, + 'umbUserObserver', + ); + }; + + async #getAppContext() { + // TODO: remove this when we get absolute urls from the server + return this.consumeContext(UMB_APP_CONTEXT, (instance) => {}).asPromise(); + } + + #setUserAvatarUrls = async (user: UmbUserDetailModel | undefined) => { + if (user?.avatarUrls?.length === 0) return; + + // TODO: remove this when we get absolute urls from the server + const serverUrl = (await this.#getAppContext()).getServerUrl(); + if (!serverUrl) return; + + this._userAvatarUrls = [ + { + scale: '1x', + url: `${serverUrl}${user?.avatarUrls?.[3]}`, + }, + { + scale: '2x', + url: `${serverUrl}${user?.avatarUrls?.[4]}`, + }, + ]; + }; + + #onAvatarUploadSubmit = (event: SubmitEvent) => { + event.preventDefault(); + + const form = event.target as HTMLFormElement; + if (!form) return; + + if (!form.checkValidity()) return; + + const formData = new FormData(form); + const avatarFile = formData.get('avatarFile') as File; + + this.#userWorkspaceContext?.uploadAvatar(avatarFile); + }; + + #deleteAvatar = async () => { + if (!this.#userWorkspaceContext) return; + const { error } = await this.#userWorkspaceContext.deleteAvatar(); + + if (!error) { + this._userAvatarUrls = []; + } + }; + + #getAvatarSrcset() { + let string = ''; + + this._userAvatarUrls?.forEach((url) => { + string += `${url.url} ${url.scale},`; + }); + return string; + } + + #hasAvatar() { + return this._userAvatarUrls.length > 0; + } + + render() { + return html` + +
+ + (WIP) + + + ${this.#hasAvatar() + ? html` + + ` + : nothing} +
+
+ `; + } + + static styles = [ + css` + :host { + display: block; + margin-bottom: var(--uui-size-space-4); + } + + #Avatar { + font-size: 75px; + place-self: center; + } + + form { + text-align: center; + display: flex; + flex-direction: column; + gap: var(--uui-size-space-2); + } + `, + ]; +} + +export default UmbUserAvatarElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-user-workspace-avatar': UmbUserAvatarElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace-editor.element.ts index dcdeccf91d..a38a00cd6d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace-editor.element.ts @@ -11,6 +11,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import './components/user-workspace-profile-settings/user-workspace-profile-settings.element.js'; import './components/user-workspace-access-settings/user-workspace-access-settings.element.js'; import './components/user-workspace-info/user-workspace-info.element.js'; +import './components/user-workspace-avatar/user-workspace-avatar.element.js'; @customElement('umb-user-workspace-editor') export class UmbUserWorkspaceEditorElement extends UmbLitElement { @@ -82,6 +83,7 @@ export class UmbUserWorkspaceEditorElement extends UmbLitElement { if (!this._user || !this.#workspaceContext) return nothing; return html` + From 9ae3bbc0dfd412cea25fbc862d644d1d4e575b23 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 1 Dec 2023 09:53:35 +0100 Subject: [PATCH 05/98] wire up change photo button --- .../user-workspace-avatar.element.ts | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts index db6d57970f..0d762470ea 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts @@ -69,18 +69,34 @@ export class UmbUserAvatarElement extends UmbLitElement { ]; }; - #onAvatarUploadSubmit = (event: SubmitEvent) => { - event.preventDefault(); + #uploadAvatar = async () => { + try { + const selectedFile = await this.#selectAvatar(); + this.#userWorkspaceContext?.uploadAvatar(selectedFile); + } catch (error) { + console.log(error); + } + }; - const form = event.target as HTMLFormElement; - if (!form) return; + #selectAvatar = async () => { + return new Promise((resolve, reject) => { + if (!this._avatarFileField) { + reject("Can't find avatar file field"); + return; + } - if (!form.checkValidity()) return; + this._avatarFileField.addEventListener('change', (event) => { + const file = event.target.files?.[0]; + if (!file) { + reject("Can't find avatar file"); + return; + } - const formData = new FormData(form); - const avatarFile = formData.get('avatarFile') as File; + resolve(file); + }); - this.#userWorkspaceContext?.uploadAvatar(avatarFile); + this._avatarFileField.click(); + }); }; #deleteAvatar = async () => { @@ -108,15 +124,14 @@ export class UmbUserAvatarElement extends UmbLitElement { render() { return html` -
+ - (WIP) - - + + ${this.#hasAvatar() ? html` Date: Fri, 1 Dec 2023 10:00:24 +0100 Subject: [PATCH 06/98] show preview if upload succeeded --- .../user-workspace-avatar/user-workspace-avatar.element.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts index 0d762470ea..b6188c22b9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts @@ -72,7 +72,11 @@ export class UmbUserAvatarElement extends UmbLitElement { #uploadAvatar = async () => { try { const selectedFile = await this.#selectAvatar(); - this.#userWorkspaceContext?.uploadAvatar(selectedFile); + const { error } = await this.#userWorkspaceContext.uploadAvatar(selectedFile); + if (!error) { + const preview = URL.createObjectURL(selectedFile); + this._userAvatarUrls = [{ url: preview, scale: '1x' }]; + } } catch (error) { console.log(error); } From aaefead02f9da424043f0f51c83d75bca8704953 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 1 Dec 2023 13:48:46 +0100 Subject: [PATCH 07/98] move update of avatar data to the repository --- .../detail/user-detail.repository.ts | 8 ++++- .../user-workspace-avatar.element.ts | 29 +++++++++++-------- .../user/workspace/user-workspace.context.ts | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/detail/user-detail.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/detail/user-detail.repository.ts index e1f92cc1e5..0121618a24 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/detail/user-detail.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/detail/user-detail.repository.ts @@ -154,7 +154,13 @@ export class UmbUserDetailRepository extends UmbUserRepositoryBase implements IU const { error } = await this.#detailSource.createAvatar(id, fileId); if (!error) { - // TODO: update store + current user + const preview = URL.createObjectURL(file); + // TODO: temp solution until we know the final avatar url format. + // the server currently returns a list of urls for different sizes. + // We need to fake a size list here. + const avatarUrls = [preview, preview, preview, preview, preview]; + this.detailStore!.updateItem(id, { avatarUrls }); + const notification = { data: { message: `Avatar uploaded` } }; this.notificationContext?.peek('positive', notification); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts index b6188c22b9..f6c651ff19 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts @@ -51,46 +51,51 @@ export class UmbUserAvatarElement extends UmbLitElement { } #setUserAvatarUrls = async (user: UmbUserDetailModel | undefined) => { - if (user?.avatarUrls?.length === 0) return; + if (!user || !user.avatarUrls || user.avatarUrls.length === 0) return; // TODO: remove this when we get absolute urls from the server + // TODO: temp hack because we can't prefix local urls with the server url. + // these are preview urls for newly uploaded avatars const serverUrl = (await this.#getAppContext()).getServerUrl(); if (!serverUrl) return; + // TODO: hack to only use size 3 and 4 from the array. The server should only return 1 url. + const isRelativeUrl = user.avatarUrls[3].startsWith('/'); + const avatarScale1 = user.avatarUrls?.[3]; + const avatarScale2 = user.avatarUrls?.[4]; + this._userAvatarUrls = [ { scale: '1x', - url: `${serverUrl}${user?.avatarUrls?.[3]}`, + url: isRelativeUrl ? serverUrl + avatarScale1 : avatarScale1, }, { scale: '2x', - url: `${serverUrl}${user?.avatarUrls?.[4]}`, + url: isRelativeUrl ? serverUrl + avatarScale2 : avatarScale2, }, ]; + debugger; }; #uploadAvatar = async () => { try { const selectedFile = await this.#selectAvatar(); - const { error } = await this.#userWorkspaceContext.uploadAvatar(selectedFile); - if (!error) { - const preview = URL.createObjectURL(selectedFile); - this._userAvatarUrls = [{ url: preview, scale: '1x' }]; - } + this.#userWorkspaceContext?.uploadAvatar(selectedFile); } catch (error) { console.log(error); } }; - #selectAvatar = async () => { - return new Promise((resolve, reject) => { + #selectAvatar() { + return new Promise((resolve, reject) => { if (!this._avatarFileField) { reject("Can't find avatar file field"); return; } this._avatarFileField.addEventListener('change', (event) => { - const file = event.target.files?.[0]; + const target = event?.target as HTMLInputElement; + const file = target.files?.[0] as File; if (!file) { reject("Can't find avatar file"); return; @@ -101,7 +106,7 @@ export class UmbUserAvatarElement extends UmbLitElement { this._avatarFileField.click(); }); - }; + } #deleteAvatar = async () => { if (!this.#userWorkspaceContext) return; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts index e419872ac3..4538a06626 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts @@ -46,7 +46,7 @@ export class UmbUserWorkspaceContext */ onUserStoreChanges(user: UmbUserDetailModel) { if (!user) return; - this.#data.update({ state: user.state }); + this.#data.update({ state: user.state, avatarUrls: user.avatarUrls }); } getEntityId(): string | undefined { From 230e447f8e7c25ad287cf0ed5dfe42d3b2d3d1ca Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 1 Dec 2023 13:49:03 +0100 Subject: [PATCH 08/98] remove debugger --- .../user-workspace-avatar/user-workspace-avatar.element.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts index f6c651ff19..bf2ad33ed7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts @@ -74,7 +74,6 @@ export class UmbUserAvatarElement extends UmbLitElement { url: isRelativeUrl ? serverUrl + avatarScale2 : avatarScale2, }, ]; - debugger; }; #uploadAvatar = async () => { From 3abb08727aaaea1efd28b860c545400b3c76a6e0 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 1 Dec 2023 15:06:49 +0100 Subject: [PATCH 09/98] render avatar for current user --- .../current-user-header-app.element.ts | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts index b5f5fe9eda..c3693bd058 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts @@ -1,5 +1,5 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, CSSResultGroup, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, CSSResultGroup, html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalManagerContext, UMB_MODAL_MANAGER_CONTEXT_TOKEN, @@ -7,12 +7,16 @@ import { } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UMB_CURRENT_USER_CONTEXT, type UmbCurrentUser } from '@umbraco-cms/backoffice/current-user'; +import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; @customElement('umb-current-user-header-app') export class UmbCurrentUserHeaderAppElement extends UmbLitElement { @state() private _currentUser?: UmbCurrentUser; + @state() + private _userAvatarUrls: Array<{ url: string; scale: string }> = []; + #currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE; #modalManagerContext?: UmbModalManagerContext; @@ -36,6 +40,8 @@ export class UmbCurrentUserHeaderAppElement extends UmbLitElement { this.#currentUserContext.currentUser, (currentUser) => { this._currentUser = currentUser; + if (!currentUser) return; + this.#setUserAvatarUrls(currentUser); }, 'umbCurrentUserObserver', ); @@ -45,6 +51,55 @@ export class UmbCurrentUserHeaderAppElement extends UmbLitElement { this.#modalManagerContext?.open(UMB_CURRENT_USER_MODAL); } + async #getAppContext() { + // TODO: remove this when we get absolute urls from the server + return this.consumeContext(UMB_APP_CONTEXT, (instance) => {}).asPromise(); + } + + #setUserAvatarUrls = async (user: UmbCurrentUser | undefined) => { + if (!user || !user.avatarUrls || user.avatarUrls.length === 0) return; + + // TODO: remove this when we get absolute urls from the server + // TODO: temp hack because we can't prefix local urls with the server url. + // these are preview urls for newly uploaded avatars + const serverUrl = (await this.#getAppContext()).getServerUrl(); + if (!serverUrl) return; + + // TODO: hack to only use size 3 and 4 from the array. The server should only return 1 url. + const isRelativeUrl = user.avatarUrls[0].startsWith('/'); + const avatarScale1 = user.avatarUrls?.[0]; + const avatarScale2 = user.avatarUrls?.[1]; + const avatarScale3 = user.avatarUrls?.[2]; + + this._userAvatarUrls = [ + { + scale: '1x', + url: isRelativeUrl ? serverUrl + avatarScale1 : avatarScale1, + }, + { + scale: '2x', + url: isRelativeUrl ? serverUrl + avatarScale2 : avatarScale2, + }, + { + scale: '3x', + url: isRelativeUrl ? serverUrl + avatarScale3 : avatarScale3, + }, + ]; + }; + + #getAvatarSrcset() { + let string = ''; + + this._userAvatarUrls?.forEach((url) => { + string += `${url.url} ${url.scale},`; + }); + return string; + } + + #hasAvatar() { + return this._userAvatarUrls.length > 0; + } + render() { return html` - + `; } From 9694acb39ff40255f29d183dadd95a7f9dbe648f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 1 Dec 2023 15:09:20 +0100 Subject: [PATCH 10/98] return error --- .../user/workspace/user-workspace.context.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts index 4538a06626..4dd645dbce 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts @@ -97,13 +97,27 @@ export class UmbUserWorkspaceContext async uploadAvatar(file: File) { const id = this.getEntityId(); if (!id) throw new Error('Id is missing'); - return this.repository.uploadAvatar(id, file); + const { error } = await this.repository.uploadAvatar(id, file); + + // TODO: temp solution until we know how to update stores + if (!error) { + await this.#reloadCurrentUser(id); + } + + return { error }; } async deleteAvatar() { const id = this.getEntityId(); if (!id) throw new Error('Id is missing'); - return this.repository.deleteAvatar(id); + const { error } = await this.repository.deleteAvatar(id); + + // TODO: temp solution until we know how to update stores + if (!error) { + await this.#reloadCurrentUser(id); + } + + return { error }; } destroy(): void { From 7dc5bd28e9229f31194e94883b69384849957244 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 1 Dec 2023 18:31:40 +0100 Subject: [PATCH 11/98] clear avatars if none is returned --- .../user/current-user/current-user-header-app.element.ts | 5 ++++- .../user-workspace-avatar/user-workspace-avatar.element.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts index c3693bd058..5ea1ff11a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts @@ -57,7 +57,10 @@ export class UmbCurrentUserHeaderAppElement extends UmbLitElement { } #setUserAvatarUrls = async (user: UmbCurrentUser | undefined) => { - if (!user || !user.avatarUrls || user.avatarUrls.length === 0) return; + if (!user || !user.avatarUrls || user.avatarUrls.length === 0) { + this._userAvatarUrls = []; + return; + } // TODO: remove this when we get absolute urls from the server // TODO: temp hack because we can't prefix local urls with the server url. diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts index bf2ad33ed7..a3bba82068 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts @@ -51,7 +51,10 @@ export class UmbUserAvatarElement extends UmbLitElement { } #setUserAvatarUrls = async (user: UmbUserDetailModel | undefined) => { - if (!user || !user.avatarUrls || user.avatarUrls.length === 0) return; + if (!user || !user.avatarUrls || user.avatarUrls.length === 0) { + this._userAvatarUrls = []; + return; + } // TODO: remove this when we get absolute urls from the server // TODO: temp hack because we can't prefix local urls with the server url. From 3e2b699c56ee886275d9f0ad88bf2fdc045337ad Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Dec 2023 21:23:07 +0100 Subject: [PATCH 12/98] this has been moved --- .../src/packages/user/utils.ts | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/utils.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/user/utils.ts deleted file mode 100644 index d880a9b44b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/user/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { InterfaceColor, InterfaceLook } from '@umbraco-cms/backoffice/external/uui'; -import { UserStateModel } from '@umbraco-cms/backoffice/backend-api'; - -interface DisplayStatus { - look: InterfaceLook; - color: InterfaceColor; - key: string; -} -const userStates: DisplayStatus[] = [ - { key: 'All', color: 'positive', look: 'secondary' }, - { key: 'Active', color: 'positive', look: 'primary' }, - { key: 'Disabled', color: 'danger', look: 'primary' }, - { key: 'LockedOut', color: 'danger', look: 'secondary' }, - { key: 'Invited', color: 'warning', look: 'primary' }, - { key: 'Inactive', color: 'warning', look: 'primary' }, -]; - -export const getDisplayStateFromUserStatus = (status?: UserStateModel): DisplayStatus => - userStates - .filter((state) => state.key === status)! - .map((state) => ({ - ...state, - key: 'state' + state.key, - }))[0]; From ed4a149c7f8acb3353189553082e1e9e8eb21b82 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 8 Feb 2024 18:12:34 +0100 Subject: [PATCH 13/98] use correct repository --- .../packages/user/user/workspace/user-workspace.context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts index 8f9f8822ce..8ddb6b1778 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts @@ -98,7 +98,7 @@ export class UmbUserWorkspaceContext async uploadAvatar(file: File) { const unique = this.getEntityId(); if (!unique) throw new Error('Id is missing'); - const { error } = await this.detailRepository.uploadAvatar(unique, file); + const { error } = await this.avatarRepository.uploadAvatar(unique, file); // TODO: temp solution until we know how to update stores if (!error) { @@ -111,7 +111,7 @@ export class UmbUserWorkspaceContext async deleteAvatar() { const unique = this.getEntityId(); if (!unique) throw new Error('Id is missing'); - const { error } = await this.detailRepository.deleteAvatar(unique); + const { error } = await this.avatarRepository.deleteAvatar(unique); // TODO: temp solution until we know how to update stores if (!error) { From dd266c0d343c2a6785654edc2090fe4ed377679d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 8 Feb 2024 18:13:16 +0100 Subject: [PATCH 14/98] fix imports --- .../layouts/default/notification-layout-default.test.ts | 1 + .../property-editor-ui-image-crops-configuration.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/notification/layouts/default/notification-layout-default.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/notification/layouts/default/notification-layout-default.test.ts index e21e5a90f2..35bb96d08f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/notification/layouts/default/notification-layout-default.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/notification/layouts/default/notification-layout-default.test.ts @@ -3,6 +3,7 @@ import type { UmbNotificationDefaultData } from './notification-layout-default.e import { UmbNotificationLayoutDefaultElement } from './notification-layout-default.element.js'; import type { UUIToastNotificationLayoutElement } from '@umbraco-cms/backoffice/external/uui'; import { UmbNotificationHandler } from '@umbraco-cms/backoffice/notification'; +import type { UmbTestRunnerWindow } from '@umbraco-cms/internal/test-utils'; describe('UmbNotificationLayoutDefault', () => { let element: UmbNotificationLayoutDefaultElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops-configuration/property-editor-ui-image-crops-configuration.test.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops-configuration/property-editor-ui-image-crops-configuration.test.ts index 36c87d4d3c..116f4cfa78 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops-configuration/property-editor-ui-image-crops-configuration.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops-configuration/property-editor-ui-image-crops-configuration.test.ts @@ -1,5 +1,6 @@ import { expect, fixture, html } from '@open-wc/testing'; import { UmbPropertyEditorUIImageCropsConfigurationElement } from './property-editor-ui-image-crops-configuration.element.js'; +import type { UmbTestRunnerWindow } from '@umbraco-cms/internal/test-utils'; //import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; describe('UmbPropertyEditorUIImageCropsConfigurationElement', () => { From 142b94ba7dd4bf404455325f27f7aeaf704901d8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 8 Feb 2024 18:14:12 +0100 Subject: [PATCH 15/98] null check --- .../user-workspace-info/user-workspace-info.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-info/user-workspace-info.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-info/user-workspace-info.element.ts index 3bd54164aa..082aa56a4d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-info/user-workspace-info.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-info/user-workspace-info.element.ts @@ -28,7 +28,7 @@ export class UmbUserWorkspaceInfoElement extends UmbLitElement { async (user) => { if (!user) return; this.#setUserInfoItems(user); - this._userDisplayState = getDisplayStateFromUserStatus(user.state); + this._userDisplayState = user.state ? getDisplayStateFromUserStatus(user.state) : null; }, 'umbUserObserver', ); From 35b43354ed4de19b6c56361c9ecf815b0d9e9560 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 8 Feb 2024 18:17:01 +0100 Subject: [PATCH 16/98] Update user-workspace-avatar.element.ts --- .../user-workspace-avatar/user-workspace-avatar.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts index a3bba82068..ba77d797e3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts @@ -1,4 +1,4 @@ -import { UmbUserDetailModel } from '../../../types.js'; +import type { UmbUserDetailModel } from '../../../types.js'; import { UMB_USER_WORKSPACE_CONTEXT } from '../../user-workspace.context.js'; import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; import { css, html, customElement, query, nothing, ifDefined, state } from '@umbraco-cms/backoffice/external/lit'; From 41d4a9e7281f9dfd89fa99d761185988968160e1 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 9 Feb 2024 10:09:28 +0100 Subject: [PATCH 17/98] remove relative url check --- .../current-user-header-app.element.ts | 24 +++---------------- .../user-workspace-avatar.element.ts | 21 ++-------------- 2 files changed, 5 insertions(+), 40 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts index 8b153b4fb0..84db43b7f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-header-app.element.ts @@ -6,7 +6,6 @@ import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UMB_CURRENT_USER_CONTEXT, type UmbCurrentUserModel } from '@umbraco-cms/backoffice/current-user'; -import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; @customElement('umb-current-user-header-app') export class UmbCurrentUserHeaderAppElement extends UmbLitElement { @@ -50,41 +49,24 @@ export class UmbCurrentUserHeaderAppElement extends UmbLitElement { this.#modalManagerContext?.open(UMB_CURRENT_USER_MODAL); } - async #getAppContext() { - // TODO: remove this when we get absolute urls from the server - return this.consumeContext(UMB_APP_CONTEXT, (instance) => {}).asPromise(); - } - #setUserAvatarUrls = async (user: UmbCurrentUserModel | undefined) => { if (!user || !user.avatarUrls || user.avatarUrls.length === 0) { this._userAvatarUrls = []; return; } - // TODO: remove this when we get absolute urls from the server - // TODO: temp hack because we can't prefix local urls with the server url. - // these are preview urls for newly uploaded avatars - const serverUrl = (await this.#getAppContext()).getServerUrl(); - if (!serverUrl) return; - - // TODO: hack to only use size 3 and 4 from the array. The server should only return 1 url. - const isRelativeUrl = user.avatarUrls[0].startsWith('/'); - const avatarScale1 = user.avatarUrls?.[0]; - const avatarScale2 = user.avatarUrls?.[1]; - const avatarScale3 = user.avatarUrls?.[2]; - this._userAvatarUrls = [ { scale: '1x', - url: isRelativeUrl ? serverUrl + avatarScale1 : avatarScale1, + url: user.avatarUrls?.[0], }, { scale: '2x', - url: isRelativeUrl ? serverUrl + avatarScale2 : avatarScale2, + url: user.avatarUrls?.[1], }, { scale: '3x', - url: isRelativeUrl ? serverUrl + avatarScale3 : avatarScale3, + url: user.avatarUrls?.[2], }, ]; }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts index ba77d797e3..794bb00290 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts @@ -1,6 +1,5 @@ import type { UmbUserDetailModel } from '../../../types.js'; import { UMB_USER_WORKSPACE_CONTEXT } from '../../user-workspace.context.js'; -import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; import { css, html, customElement, query, nothing, ifDefined, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -45,36 +44,20 @@ export class UmbUserAvatarElement extends UmbLitElement { ); }; - async #getAppContext() { - // TODO: remove this when we get absolute urls from the server - return this.consumeContext(UMB_APP_CONTEXT, (instance) => {}).asPromise(); - } - #setUserAvatarUrls = async (user: UmbUserDetailModel | undefined) => { if (!user || !user.avatarUrls || user.avatarUrls.length === 0) { this._userAvatarUrls = []; return; } - // TODO: remove this when we get absolute urls from the server - // TODO: temp hack because we can't prefix local urls with the server url. - // these are preview urls for newly uploaded avatars - const serverUrl = (await this.#getAppContext()).getServerUrl(); - if (!serverUrl) return; - - // TODO: hack to only use size 3 and 4 from the array. The server should only return 1 url. - const isRelativeUrl = user.avatarUrls[3].startsWith('/'); - const avatarScale1 = user.avatarUrls?.[3]; - const avatarScale2 = user.avatarUrls?.[4]; - this._userAvatarUrls = [ { scale: '1x', - url: isRelativeUrl ? serverUrl + avatarScale1 : avatarScale1, + url: user.avatarUrls?.[3], }, { scale: '2x', - url: isRelativeUrl ? serverUrl + avatarScale2 : avatarScale2, + url: user.avatarUrls?.[4], }, ]; }; From 9ec078f15dfcc1d56caa5c906b0e3e9b01191288 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 9 Feb 2024 12:47:04 +0100 Subject: [PATCH 18/98] update user detail store instead of reloading the current user --- .../avatar/user-avatar.repository.ts | 29 ++++++---- .../user/workspace/user-workspace.context.ts | 54 +++++-------------- 2 files changed, 34 insertions(+), 49 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/avatar/user-avatar.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/avatar/user-avatar.repository.ts index 11f1e15852..3f11546de1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/avatar/user-avatar.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/avatar/user-avatar.repository.ts @@ -7,6 +7,7 @@ import { UmbTemporaryFileRepository } from '@umbraco-cms/backoffice/temporary-fi export class UmbUserAvatarRepository extends UmbUserRepositoryBase { #temporaryFileRepository: UmbTemporaryFileRepository; #avatarSource: UmbUserAvatarServerDataSource; + #avatarFile: File | null = null; constructor(host: UmbControllerHost) { super(host); @@ -17,13 +18,13 @@ export class UmbUserAvatarRepository extends UmbUserRepositoryBase { /** * Uploads an avatar for the user with the given id - * @param {string} userId + * @param {string} userUnique * @param {File} file * @return {Promise} * @memberof UmbUserRepository */ - async uploadAvatar(userId: string, file: File) { - if (!userId) throw new Error('Id is missing'); + async uploadAvatar(userUnique: string, file: File) { + if (!userUnique) throw new Error('Id is missing'); await this.init; // upload temp file @@ -31,10 +32,15 @@ export class UmbUserAvatarRepository extends UmbUserRepositoryBase { await this.#temporaryFileRepository.upload(fileId, file); // assign temp file to avatar - const { error } = await this.#avatarSource.createAvatar(userId, fileId); + const { error } = await this.#avatarSource.createAvatar(userUnique, fileId); if (!error) { // TODO: update store + current user + const localUrl = URL.createObjectURL(file); + + // The server returns 5 different sizes of the avatar, so we need to mimick that here + this.detailStore?.updateItem(userUnique, { avatarUrls: [localUrl, localUrl, localUrl, localUrl, localUrl] }); + const notification = { data: { message: `Avatar uploaded` } }; this.notificationContext?.peek('positive', notification); } @@ -44,24 +50,29 @@ export class UmbUserAvatarRepository extends UmbUserRepositoryBase { /** * Removes the avatar for the user with the given id - * @param {string} id + * @param {string} userUnique * @return {Promise} * @memberof UmbUserRepository */ - async deleteAvatar(id: string) { - if (!id) throw new Error('Id is missing'); + async deleteAvatar(userUnique: string) { + if (!userUnique) throw new Error('Id is missing'); await this.init; - const { error } = await this.#avatarSource.deleteAvatar(id); + const { error } = await this.#avatarSource.deleteAvatar(userUnique); if (!error) { - // TODO: update store + current user + this.detailStore?.updateItem(userUnique, { avatarUrls: [] }); + const notification = { data: { message: `Avatar deleted` } }; this.notificationContext?.peek('positive', notification); } return { error }; } + + destroy() { + super.destroy(); + } } export default UmbUserAvatarRepository; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts index 8ddb6b1778..05e368bd81 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts @@ -7,9 +7,7 @@ import type { UmbSaveableWorkspaceContextInterface } from '@umbraco-cms/backoffi import { UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; -import { UmbContextConsumerController, UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user'; -import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export class UmbUserWorkspaceContext extends UmbEditableWorkspaceContextBase @@ -18,14 +16,8 @@ export class UmbUserWorkspaceContext public readonly detailRepository: UmbUserDetailRepository = new UmbUserDetailRepository(this); public readonly avatarRepository: UmbUserAvatarRepository = new UmbUserAvatarRepository(this); - #currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE; - constructor(host: UmbControllerHost) { super(host, UMB_USER_WORKSPACE_ALIAS); - - new UmbContextConsumerController(host, UMB_CURRENT_USER_CONTEXT, (instance) => { - this.#currentUserContext = instance; - }); } #data = new UmbObjectState(undefined); @@ -74,51 +66,33 @@ export class UmbUserWorkspaceContext if (!this.#data.value) throw new Error('Data is missing'); if (!this.#data.value.unique) throw new Error('Unique is missing'); + let newData = undefined; + if (this.getIsNew()) { - await this.detailRepository.create(this.#data.value); + const { data } = await this.detailRepository.create(this.#data.value); + newData = data; } else { - await this.detailRepository.save(this.#data.value); + const { data } = await this.detailRepository.save(this.#data.value); + newData = data; } - // If it went well, then its not new anymore?. - this.setIsNew(false); - // If we are saving the current user, we need to refetch it - await this.#reloadCurrentUser(this.#data.value.unique); - } - - async #reloadCurrentUser(savedUserUnique: string): Promise { - if (!this.#currentUserContext) return; - const currentUser = await firstValueFrom(this.#currentUserContext.currentUser); - if (currentUser?.unique === savedUserUnique) { - await this.#currentUserContext.requestCurrentUser(); + if (newData) { + this.#data.setValue(newData); + this.saveComplete(newData); } } // TODO: implement upload progress - async uploadAvatar(file: File) { + uploadAvatar(file: File) { const unique = this.getEntityId(); if (!unique) throw new Error('Id is missing'); - const { error } = await this.avatarRepository.uploadAvatar(unique, file); - - // TODO: temp solution until we know how to update stores - if (!error) { - await this.#reloadCurrentUser(unique); - } - - return { error }; + return this.avatarRepository.uploadAvatar(unique, file); } - async deleteAvatar() { + deleteAvatar() { const unique = this.getEntityId(); if (!unique) throw new Error('Id is missing'); - const { error } = await this.avatarRepository.deleteAvatar(unique); - - // TODO: temp solution until we know how to update stores - if (!error) { - await this.#reloadCurrentUser(unique); - } - - return { error }; + return this.avatarRepository.deleteAvatar(unique); } destroy(): void { From 066f27d52619027b1329d2d53ef63f74f72c993b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 9 Feb 2024 13:24:55 +0100 Subject: [PATCH 19/98] use context base --- .../packages/user/current-user/current-user.context.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts index b84106b75e..bee68167ff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts @@ -1,14 +1,14 @@ import type { UmbCurrentUserModel } from './types.js'; import { UmbCurrentUserRepository } from './repository/index.js'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { umbLocalizationRegistry } from '@umbraco-cms/backoffice/localization'; -export class UmbCurrentUserContext extends UmbBaseController { +export class UmbCurrentUserContext extends UmbContextBase { #currentUser = new UmbObjectState(undefined); readonly currentUser = this.#currentUser.asObservable(); @@ -18,7 +18,7 @@ export class UmbCurrentUserContext extends UmbBaseController { #currentUserRepository = new UmbCurrentUserRepository(this); constructor(host: UmbControllerHost) { - super(host); + super(host, UMB_CURRENT_USER_CONTEXT); this.consumeContext(UMB_AUTH_CONTEXT, (instance) => { this.#authContext = instance; @@ -29,8 +29,6 @@ export class UmbCurrentUserContext extends UmbBaseController { if (!currentLanguageIsoCode) return; umbLocalizationRegistry.loadLanguage(currentLanguageIsoCode); }); - - this.provideContext(UMB_CURRENT_USER_CONTEXT, this); } async requestCurrentUser() { From 371d46d3dca6e200397078451328fed1ce599686 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 10:28:19 +0100 Subject: [PATCH 20/98] fix import after merge --- .../user-workspace-avatar/user-workspace-avatar.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts index 794bb00290..c047539e6a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-avatar/user-workspace-avatar.element.ts @@ -1,7 +1,7 @@ import type { UmbUserDetailModel } from '../../../types.js'; import { UMB_USER_WORKSPACE_CONTEXT } from '../../user-workspace.context.js'; import { css, html, customElement, query, nothing, ifDefined, state } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-user-workspace-avatar') export class UmbUserAvatarElement extends UmbLitElement { From df7f0a665ecc6602f86dc947c45d185b564298da Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 11:58:18 +0100 Subject: [PATCH 21/98] add current user store --- .../repository/current-user.store.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.store.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.store.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.store.ts new file mode 100644 index 0000000000..629d346788 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.store.ts @@ -0,0 +1,52 @@ +import type { UmbCurrentUserModel } from '../types.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; + +export class UmbCurrentUserStore extends UmbContextBase { + #data = new UmbObjectState(undefined); + readonly data = this.#data.asObservable(); + + constructor(host: UmbControllerHostElement) { + super(host, UMB_CURRENT_USER_STORE_CONTEXT.toString()); + } + + /** + * Get the current user + * @readonly + * @type {UmbCurrentUserModel} + * @memberof UmbCurrentUserStore + */ + get() { + return this.#data.getValue(); + } + + /** + * Set the current user + * @param {UmbCurrentUserModel} data + * @memberof UmbCurrentUserStore + */ + set(data: UmbCurrentUserModel) { + this.#data.setValue(data); + } + + /** + * Update the current user + * @param {Partial} data + * @memberof UmbCurrentUserStore + */ + update(data: Partial) { + this.#data.update(data); + } + + /** + * Clear the current user + * @memberof UmbCurrentUserStore + */ + clear() { + this.#data.setValue(undefined); + } +} + +export const UMB_CURRENT_USER_STORE_CONTEXT = new UmbContextToken('UmbCurrentUserStore'); From 04ab4926060125a07c27e9f1d334c349fde5fd48 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 11:59:01 +0100 Subject: [PATCH 22/98] use store in repository --- .../repository/current-user.repository.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts index 3c239b15fe..9b83699ed7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts @@ -1,4 +1,5 @@ import { UmbCurrentUserServerDataSource } from './current-user.server.data-source.js'; +import { UMB_CURRENT_USER_STORE_CONTEXT } from './current-user.store.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; @@ -10,11 +11,19 @@ import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; */ export class UmbCurrentUserRepository extends UmbRepositoryBase { #currentUserSource: UmbCurrentUserServerDataSource; + #currentUserStore?: typeof UMB_CURRENT_USER_STORE_CONTEXT.TYPE; + #init: Promise; constructor(host: UmbControllerHost) { super(host); this.#currentUserSource = new UmbCurrentUserServerDataSource(host); + + this.#init = Promise.all([ + this.consumeContext(UMB_CURRENT_USER_STORE_CONTEXT, (instance) => { + this.#currentUserStore = instance; + }).asPromise(), + ]); } /** @@ -23,8 +32,14 @@ export class UmbCurrentUserRepository extends UmbRepositoryBase { * @memberof UmbCurrentUserRepository */ async requestCurrentUser() { - // TODO: add observable option - return this.#currentUserSource.getCurrentUser(); + await this.#init; + const { data, error } = await this.#currentUserSource.getCurrentUser(); + + if (data) { + this.#currentUserStore?.set(data); + } + + return { data, error, asObservable: () => this.#currentUserStore!.get() }; } } From b3097734a94cbd5848aa5192a6a9d5d1101f840d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 11:59:22 +0100 Subject: [PATCH 23/98] align context method names --- .../packages/user/current-user/current-user.context.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts index bee68167ff..f00f1388e3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts @@ -31,12 +31,14 @@ export class UmbCurrentUserContext extends UmbContextBase }); } - async requestCurrentUser() { + /** + * Loads the current user + */ + async load() { const { data } = await this.#currentUserRepository.requestCurrentUser(); if (data) { - // TODO: observe current user - this.#currentUser.setValue(data); + this.#currentUser?.setValue(data); } } @@ -55,7 +57,7 @@ export class UmbCurrentUserContext extends UmbContextBase if (!this.#authContext) return; this.observe(this.#authContext.isAuthorized, (isAuthorized) => { if (isAuthorized) { - this.requestCurrentUser(); + this.load(); } }); } From 233e16111553d61e0de6ffa9e9ca61569c157904 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 11:59:34 +0100 Subject: [PATCH 24/98] register and exports --- .../src/packages/user/current-user/manifests.ts | 11 ++++++----- .../packages/user/current-user/repository/index.ts | 1 + .../user/current-user/repository/manifests.ts | 14 +++++++++++--- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts index 9b279495bb..bbc2fcd1b0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts @@ -1,14 +1,15 @@ import { UmbCurrentUserContext } from './current-user.context.js'; import { manifests as modalManifests } from './modals/manifests.js'; import { manifests as userProfileAppsManifests } from './user-profile-apps/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const headerApps: Array = [ { type: 'store', - alias: 'Umb.Store.CurrentUser', - name: 'Current User Store', - js: () => import('./current-user-history.store.js'), + alias: 'Umb.Store.CurrentUser.History', + name: 'Current User History Store', + api: () => import('./current-user-history.store.js'), }, { type: 'globalContext', @@ -20,7 +21,7 @@ export const headerApps: Array = [ type: 'headerApp', alias: 'Umb.HeaderApp.CurrentUser', name: 'Current User', - js: () => import('./current-user-header-app.element.js'), + element: () => import('./current-user-header-app.element.js'), weight: 0, meta: { label: 'TODO: how should we enable this to not be set.', @@ -30,4 +31,4 @@ export const headerApps: Array = [ }, ]; -export const manifests = [...headerApps, ...modalManifests, ...userProfileAppsManifests]; +export const manifests = [...headerApps, ...modalManifests, ...repositoryManifests, ...userProfileAppsManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts index a5a259f95c..b00cb02a9e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts @@ -1,2 +1,3 @@ export { UmbCurrentUserRepository } from './current-user.repository.js'; export { UMB_CURRENT_USER_REPOSITORY_ALIAS } from './manifests.js'; +export { UmbCurrentUserStore, UMB_CURRENT_USER_STORE_CONTEXT } from './current-user.store.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/manifests.ts index ca6754a084..decd09acc3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/manifests.ts @@ -1,13 +1,21 @@ import { UmbCurrentUserRepository } from './current-user.repository.js'; -import type { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbCurrentUserStore } from './current-user.store.js'; +import type { ManifestRepository, ManifestStore } from '@umbraco-cms/backoffice/extension-registry'; export const UMB_CURRENT_USER_REPOSITORY_ALIAS = 'Umb.Repository.CurrentUser'; -const avatarRepository: ManifestRepository = { +const repository: ManifestRepository = { type: 'repository', alias: UMB_CURRENT_USER_REPOSITORY_ALIAS, name: 'Current User Repository', api: UmbCurrentUserRepository, }; -export const manifests = [avatarRepository]; +const store: ManifestStore = { + type: 'store', + alias: 'Umb.Store.CurrentUser', + name: 'Current User Store', + api: UmbCurrentUserStore, +}; + +export const manifests = [repository, store]; From eef55cc28e87f9aac6fce0182f11be41c0c395fc Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 13:10:01 +0100 Subject: [PATCH 25/98] reorganize user profile apps --- ...gin-providers-user-profile-app.element.ts} | 8 +-- .../current-user/external-login/manifests.ts | 16 ++++++ ...-user-history-user-profile-app.element.ts} | 12 ++--- .../current-user-history.store.ts | 0 .../user/current-user/history/manifests.ts | 22 +++++++++ .../src/packages/user/current-user/index.ts | 3 +- .../packages/user/current-user/manifests.ts | 21 +++++--- ...-user-profile-user-profile-app.element.ts} | 8 +-- .../user/current-user/profile/manifests.ts | 16 ++++++ ...nt-user-theme-user-profile-app.element.ts} | 8 +-- .../user/current-user/theme/manifests.ts | 16 ++++++ .../user-profile-apps/manifests.ts | 49 ------------------- 12 files changed, 102 insertions(+), 77 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/user/current-user/{user-profile-apps/user-profile-app-external-login-providers.element.ts => external-login/external-login-providers-user-profile-app.element.ts} (65%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/manifests.ts rename src/Umbraco.Web.UI.Client/src/packages/user/current-user/{user-profile-apps/user-profile-app-history.element.ts => history/current-user-history-user-profile-app.element.ts} (86%) rename src/Umbraco.Web.UI.Client/src/packages/user/current-user/{ => history}/current-user-history.store.ts (100%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/history/manifests.ts rename src/Umbraco.Web.UI.Client/src/packages/user/current-user/{user-profile-apps/user-profile-app-profile.element.ts => profile/current-user-profile-user-profile-app.element.ts} (89%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/manifests.ts rename src/Umbraco.Web.UI.Client/src/packages/user/current-user/{user-profile-apps/user-profile-app-themes.element.ts => theme/current-user-theme-user-profile-app.element.ts} (87%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/theme/manifests.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-external-login-providers.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/external-login-providers-user-profile-app.element.ts similarity index 65% rename from src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-external-login-providers.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/external-login-providers-user-profile-app.element.ts index 1412866a8a..c0f06e6dc3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-external-login-providers.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/external-login-providers-user-profile-app.element.ts @@ -2,8 +2,8 @@ import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -@customElement('umb-user-profile-app-external-login-providers') -export class UmbUserProfileAppExternalLoginProvidersElement extends UmbLitElement { +@customElement('umb-external-login-providers-user-profile-app') +export class UmbExternalLoginProvidersUserProfileAppElement extends UmbLitElement { render() { return html` @@ -16,10 +16,10 @@ export class UmbUserProfileAppExternalLoginProvidersElement extends UmbLitElemen static styles = [UmbTextStyles, css``]; } -export default UmbUserProfileAppExternalLoginProvidersElement; +export default UmbExternalLoginProvidersUserProfileAppElement; declare global { interface HTMLElementTagNameMap { - 'umb-user-profile-app-external-login-providers': UmbUserProfileAppExternalLoginProvidersElement; + 'umb-external-login-providers-user-profile-app': UmbExternalLoginProvidersUserProfileAppElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/manifests.ts new file mode 100644 index 0000000000..2295688d88 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/manifests.ts @@ -0,0 +1,16 @@ +import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry'; + +export const userProfileApps: Array = [ + { + type: 'userProfileApp', + alias: 'Umb.UserProfileApp.CurrentUser.ExternalLoginProviders', + name: 'External Login Providers User Profile App', + element: () => import('./external-login-providers-user-profile-app.element.js'), + weight: 800, + meta: { + label: 'External Login Providers User Profile App', + pathname: 'externalLoginProviders', + }, + }, +]; +export const manifests = [...userProfileApps]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-history.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/history/current-user-history-user-profile-app.element.ts similarity index 86% rename from src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-history.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/current-user/history/current-user-history-user-profile-app.element.ts index bc58fe7156..c2b58fac26 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-history.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/history/current-user-history-user-profile-app.element.ts @@ -1,11 +1,11 @@ -import type { UmbCurrentUserHistoryItem, UmbCurrentUserHistoryStore } from '../current-user-history.store.js'; -import { UMB_CURRENT_USER_HISTORY_STORE_CONTEXT } from '../current-user-history.store.js'; +import type { UmbCurrentUserHistoryItem, UmbCurrentUserHistoryStore } from './current-user-history.store.js'; +import { UMB_CURRENT_USER_HISTORY_STORE_CONTEXT } from './current-user-history.store.js'; import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -@customElement('umb-user-profile-app-history') -export class UmbUserProfileAppHistoryElement extends UmbLitElement { +@customElement('umb-current-user-history-user-profile-app') +export class UmbCurrentUserHistoryUserProfileAppElement extends UmbLitElement { @state() private _history: Array = []; @@ -109,10 +109,10 @@ export class UmbUserProfileAppHistoryElement extends UmbLitElement { ]; } -export default UmbUserProfileAppHistoryElement; +export default UmbCurrentUserHistoryUserProfileAppElement; declare global { interface HTMLElementTagNameMap { - 'umb-user-dashboard-test': UmbUserProfileAppHistoryElement; + 'umb-current-user-history-user-profile-app': UmbCurrentUserHistoryUserProfileAppElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-history.store.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/history/current-user-history.store.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user-history.store.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/current-user/history/current-user-history.store.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/history/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/history/manifests.ts new file mode 100644 index 0000000000..491eb883eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/history/manifests.ts @@ -0,0 +1,22 @@ +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const userProfileApps: Array = [ + { + type: 'userProfileApp', + alias: 'Umb.UserProfileApp.CurrentUser.History', + name: 'Current User History User Profile App', + element: () => import('../history/current-user-history-user-profile-app.element.js'), + weight: 100, + meta: { + label: 'History', + pathname: 'history', + }, + }, + { + type: 'store', + alias: 'Umb.Store.CurrentUser.History', + name: 'Current User History Store', + api: () => import('./current-user-history.store.js'), + }, +]; +export const manifests = [...userProfileApps]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts index 8d63217a6a..4327ecb296 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts @@ -1,5 +1,4 @@ -// TODO:Do not export store, but instead export future repository -export * from './current-user-history.store.js'; +export * from './history/current-user-history.store.js'; export * from './utils/index.js'; export * from './current-user.context.js'; export * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts index bbc2fcd1b0..4f65d8b7c7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts @@ -1,16 +1,13 @@ import { UmbCurrentUserContext } from './current-user.context.js'; import { manifests as modalManifests } from './modals/manifests.js'; -import { manifests as userProfileAppsManifests } from './user-profile-apps/manifests.js'; +import { manifests as externalLoginProviderManifests } from './external-login/manifests.js'; +import { manifests as historyManifests } from './history/manifests.js'; +import { manifests as profileManifests } from './profile/manifests.js'; +import { manifests as themeManifests } from './theme/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const headerApps: Array = [ - { - type: 'store', - alias: 'Umb.Store.CurrentUser.History', - name: 'Current User History Store', - api: () => import('./current-user-history.store.js'), - }, { type: 'globalContext', alias: 'Umb.GlobalContext.CurrentUser', @@ -31,4 +28,12 @@ export const headerApps: Array = [ }, ]; -export const manifests = [...headerApps, ...modalManifests, ...repositoryManifests, ...userProfileAppsManifests]; +export const manifests = [ + ...externalLoginProviderManifests, + ...headerApps, + ...historyManifests, + ...modalManifests, + ...profileManifests, + ...repositoryManifests, + ...themeManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-profile.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/current-user-profile-user-profile-app.element.ts similarity index 89% rename from src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-profile.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/current-user-profile-user-profile-app.element.ts index d314ef0f26..39e23cd365 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-profile.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/current-user-profile-user-profile-app.element.ts @@ -5,8 +5,8 @@ import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; import { UMB_CHANGE_PASSWORD_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UMB_CURRENT_USER_CONTEXT, type UmbCurrentUserModel } from '@umbraco-cms/backoffice/current-user'; -@customElement('umb-user-profile-app-profile') -export class UmbUserProfileAppProfileElement extends UmbLitElement { +@customElement('umb-current-user-profile-user-profile-app') +export class UmbCurrentUserProfileUserProfileAppElement extends UmbLitElement { @state() private _currentUser?: UmbCurrentUserModel; @@ -73,10 +73,10 @@ export class UmbUserProfileAppProfileElement extends UmbLitElement { static styles = [UmbTextStyles, css``]; } -export default UmbUserProfileAppProfileElement; +export default UmbCurrentUserProfileUserProfileAppElement; declare global { interface HTMLElementTagNameMap { - 'umb-user-profile-app-profile': UmbUserProfileAppProfileElement; + 'umb-current-user-profile-user-profile-app': UmbCurrentUserProfileUserProfileAppElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/manifests.ts new file mode 100644 index 0000000000..632490be5f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/manifests.ts @@ -0,0 +1,16 @@ +import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry'; + +export const userProfileApps: Array = [ + { + type: 'userProfileApp', + alias: 'Umb.UserProfileApp.CurrentUser.Profile', + name: 'Current User Profile User Profile App', + element: () => import('./current-user-profile-user-profile-app.element.js'), + weight: 900, + meta: { + label: 'Current User Profile User Profile App', + pathname: 'profile', + }, + }, +]; +export const manifests = [...userProfileApps]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-themes.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/theme/current-user-theme-user-profile-app.element.ts similarity index 87% rename from src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-themes.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/current-user/theme/current-user-theme-user-profile-app.element.ts index 56c556d1ff..c3c294372e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/user-profile-app-themes.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/theme/current-user-theme-user-profile-app.element.ts @@ -6,8 +6,8 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { ManifestTheme } from '@umbraco-cms/backoffice/extension-registry'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -@customElement('umb-user-profile-app-themes') -export class UmbUserProfileAppThemesElement extends UmbLitElement { +@customElement('umb-current-user-theme-user-profile-app') +export class UmbCurrentUserThemeUserProfileAppElement extends UmbLitElement { #themeContext?: UmbThemeContext; @state() @@ -77,10 +77,10 @@ export class UmbUserProfileAppThemesElement extends UmbLitElement { ]; } -export default UmbUserProfileAppThemesElement; +export default UmbCurrentUserThemeUserProfileAppElement; declare global { interface HTMLElementTagNameMap { - 'umb-user-profile-app-themes': UmbUserProfileAppThemesElement; + 'umb-current-user-theme-user-profile-app': UmbCurrentUserThemeUserProfileAppElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/theme/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/theme/manifests.ts new file mode 100644 index 0000000000..19c24ea470 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/theme/manifests.ts @@ -0,0 +1,16 @@ +import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry'; + +export const userProfileApps: Array = [ + { + type: 'userProfileApp', + alias: 'Umb.UserProfileApp.CurrentUser.Theme', + name: 'Current User Theme User Profile App', + element: () => import('./current-user-theme-user-profile-app.element.js'), + weight: 200, + meta: { + label: 'Current User Theme User Profile App', + pathname: 'themes', + }, + }, +]; +export const manifests = [...userProfileApps]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/manifests.ts deleted file mode 100644 index 98d5a2e84e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/user-profile-apps/manifests.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry'; - -export const userProfileApps: Array = [ - { - type: 'userProfileApp', - alias: 'Umb.UserProfileApp.profile', - name: 'Profile User Profile App', - js: () => import('./user-profile-app-profile.element.js'), - weight: 900, - meta: { - label: 'Profile User Profile App', - pathname: 'profile', - }, - }, - { - type: 'userProfileApp', - alias: 'Umb.UserProfileApp.ExternalLoginProviders', - name: 'External Login Providers User Profile App', - js: () => import('./user-profile-app-external-login-providers.element.js'), - weight: 800, - meta: { - label: 'External Login Providers User Profile App', - pathname: 'externalLoginProviders', - }, - }, - { - type: 'userProfileApp', - alias: 'Umb.UserProfileApp.Themes', - name: 'Themes User Profile App', - js: () => import('./user-profile-app-themes.element.js'), - weight: 200, - meta: { - label: 'Themes User Profile App', - pathname: 'themes', - }, - }, - { - type: 'userProfileApp', - alias: 'Umb.UserProfileApp.History', - name: 'History User Profile App', - js: () => import('./user-profile-app-history.element.js'), - weight: 100, - meta: { - label: 'History User Profile App', - pathname: 'history', - }, - }, -]; -export const manifests = [...userProfileApps]; From 8b12c0c9f686cfbb2dcd95e50cbb2a27c97e4935 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 13:19:33 +0100 Subject: [PATCH 26/98] return an observable --- .../user/current-user/repository/current-user.repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts index 9b83699ed7..a48e199660 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts @@ -39,7 +39,7 @@ export class UmbCurrentUserRepository extends UmbRepositoryBase { this.#currentUserStore?.set(data); } - return { data, error, asObservable: () => this.#currentUserStore!.get() }; + return { data, error, asObservable: () => this.#currentUserStore!.data }; } } From 9d796fe62cb83f6ff919a359f568a9df83d8c212 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 13:19:43 +0100 Subject: [PATCH 27/98] observe store data --- .../packages/user/current-user/current-user.context.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts index f00f1388e3..612b6d5e8b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts @@ -35,10 +35,12 @@ export class UmbCurrentUserContext extends UmbContextBase * Loads the current user */ async load() { - const { data } = await this.#currentUserRepository.requestCurrentUser(); + const { asObservable } = await this.#currentUserRepository.requestCurrentUser(); - if (data) { - this.#currentUser?.setValue(data); + if (asObservable) { + this.observe(asObservable(), (currentUser) => { + this.#currentUser?.setValue(currentUser); + }); } } From e8c25706536b0b39b8a8d3e5b51669c58588af6a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 14:52:36 +0100 Subject: [PATCH 28/98] use uniques --- .../repository/current-user.server.data-source.ts | 4 ++-- .../src/packages/user/current-user/types.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts index 5038b01a3e..50bae0fe8c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts @@ -35,8 +35,8 @@ export class UmbCurrentUserServerDataSource { userName: data.userName, name: data.name, languageIsoCode: data.languageIsoCode || 'en-us', // TODO: make global variable - documentStartNodeIds: data.documentStartNodeIds, - mediaStartNodeIds: data.mediaStartNodeIds, + documentStartNodeUniques: data.documentStartNodeIds, + mediaStartNodeUniques: data.mediaStartNodeIds, avatarUrls: data.avatarUrls, languages: data.languages, hasAccessToAllLanguages: data.hasAccessToAllLanguages, diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/types.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/types.ts index 21a3296d5a..0cb54ad319 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/types.ts @@ -4,8 +4,8 @@ export interface UmbCurrentUserModel { userName: string; name: string; languageIsoCode: string; - documentStartNodeIds: Array; - mediaStartNodeIds: Array; + documentStartNodeUniques: Array; + mediaStartNodeUniques: Array; avatarUrls: Array; languages: Array; hasAccessToAllLanguages: boolean; From c45dc851a4a0038f7de416bd9fd13ca771be3e36 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 14:52:49 +0100 Subject: [PATCH 29/98] sync current user data --- .../repository/current-user.store.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.store.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.store.ts index 629d346788..47806bb260 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.store.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.store.ts @@ -1,4 +1,6 @@ import type { UmbCurrentUserModel } from '../types.js'; +import type { UmbUserDetailModel } from '@umbraco-cms/backoffice/user'; +import { UMB_USER_DETAIL_STORE_CONTEXT } from '@umbraco-cms/backoffice/user'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; @@ -10,6 +12,10 @@ export class UmbCurrentUserStore extends UmbContextBase { constructor(host: UmbControllerHostElement) { super(host, UMB_CURRENT_USER_STORE_CONTEXT.toString()); + + this.consumeContext(UMB_USER_DETAIL_STORE_CONTEXT, (instance) => { + this.observe(instance?.all(), (users) => this.#onUserDetailStoreUpdate(users)); + }); } /** @@ -47,6 +53,26 @@ export class UmbCurrentUserStore extends UmbContextBase { clear() { this.#data.setValue(undefined); } + + #onUserDetailStoreUpdate = (users: Array) => { + const currentUser = this.get(); + if (!currentUser) return; + + const updatedCurrentUser = users.find((user) => user.unique === currentUser.unique); + if (!updatedCurrentUser) return; + + const mappedCurrentUser: Partial = { + email: updatedCurrentUser.email, + userName: updatedCurrentUser.userName, + name: updatedCurrentUser.name, + languageIsoCode: updatedCurrentUser.languageIsoCode || '', // TODO: default value? + documentStartNodeUniques: updatedCurrentUser.documentStartNodeUniques, + mediaStartNodeUniques: updatedCurrentUser.mediaStartNodeUniques, + avatarUrls: updatedCurrentUser.avatarUrls, + }; + + this.update(mappedCurrentUser); + }; } export const UMB_CURRENT_USER_STORE_CONTEXT = new UmbContextToken('UmbCurrentUserStore'); From 35defed69bbc23c518de876f94cef25c2a0f78d0 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 15:19:24 +0100 Subject: [PATCH 30/98] render avatar in user collection grid view --- .../grid/user-grid-collection-view.element.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts index 23d9f8fd01..0f58d7e51f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts @@ -76,6 +76,27 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement { } #renderUserCard(user: UmbUserDetailModel) { + const avatarUrls = [ + { + scale: '1x', + url: user.avatarUrls?.[0], + }, + { + scale: '2x', + url: user.avatarUrls?.[1], + }, + { + scale: '3x', + url: user.avatarUrls?.[2], + }, + ]; + + let avatarSrcset = ''; + + avatarUrls.forEach((url) => { + avatarSrcset += `${url.url} ${url.scale},`; + }); + return html` this.#onSelect(user)} @deselected=${() => this.#onDeselect(user)}> ${this.#renderUserTag(user)} ${this.#renderUserGroupNames(user)} ${this.#renderUserLoginDate(user)} + + 0 ? avatarUrls[0].url : undefined)} + img-srcset=${ifDefined(user.avatarUrls.length > 0 ? avatarSrcset : undefined)}> `; } From 5774408623d198387ea03dbdd5b83162ea02756f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 15:28:53 +0100 Subject: [PATCH 31/98] remove interface from "generic" store extension --- .../packages/core/extension-registry/models/store.model.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/store.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/store.model.ts index b092a15597..6dcd6f0a2c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/store.model.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/store.model.ts @@ -1,8 +1,8 @@ import type { ManifestApi } from '@umbraco-cms/backoffice/extension-api'; -import type { UmbItemStore, UmbStoreBase } from '@umbraco-cms/backoffice/store'; +import type { UmbItemStore } from '@umbraco-cms/backoffice/store'; import type { UmbTreeStore } from '@umbraco-cms/backoffice/tree'; -export interface ManifestStore extends ManifestApi { +export interface ManifestStore extends ManifestApi { type: 'store'; } // TODO: TREE STORE TYPE PROBLEM: Provide a base tree item type? From 6d566147a9af19ee5f7c5a042bf8ec81043cbcbf Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 19 Feb 2024 15:46:42 +0100 Subject: [PATCH 32/98] render avatar in table --- .../user-table-name-column-layout.element.ts | 31 +++++++++++++++++-- .../user-table-collection-view.element.ts | 6 ++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/column-layouts/name/user-table-name-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/column-layouts/name/user-table-name-column-layout.element.ts index a288dc49c1..620eee98c7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/column-layouts/name/user-table-name-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/column-layouts/name/user-table-name-column-layout.element.ts @@ -1,4 +1,4 @@ -import { html, LitElement, customElement, property } from '@umbraco-cms/backoffice/external/lit'; +import { html, LitElement, customElement, property, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import type { UmbTableColumn, UmbTableItem } from '@umbraco-cms/backoffice/components'; @customElement('umb-user-table-name-column-layout') @@ -13,9 +13,34 @@ export class UmbUserTableNameColumnLayoutElement extends LitElement { value!: any; render() { + const avatarUrls = [ + { + scale: '1x', + url: this.value.avatarUrls?.[0], + }, + { + scale: '2x', + url: this.value.avatarUrls?.[1], + }, + { + scale: '3x', + url: this.value.avatarUrls?.[2], + }, + ]; + + let avatarSrcset = ''; + + avatarUrls.forEach((url) => { + avatarSrcset += `${url.url} ${url.scale},`; + }); + return html` `; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/user-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/user-table-collection-view.element.ts index 6cb43059d4..144849bd97 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/user-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/user-table-collection-view.element.ts @@ -87,8 +87,8 @@ export class UmbUserTableCollectionViewElement extends UmbLitElement { async #observeUserGroups() { if (this._users.length === 0) return; - const userGroupsIds = [...new Set(this._users.flatMap((user) => user.userGroupUniques))]; - const { asObservable } = await this.#userGroupItemRepository.requestItems(userGroupsIds); + const userGroupsUniques = [...new Set(this._users.flatMap((user) => user.userGroupUniques))]; + const { asObservable } = await this.#userGroupItemRepository.requestItems(userGroupsUniques); this.observe( asObservable(), (userGroups) => { @@ -116,7 +116,9 @@ export class UmbUserTableCollectionViewElement extends UmbLitElement { { columnAlias: 'userName', value: { + unique: user.unique, name: user.name, + avatarUrls: user.avatarUrls, }, }, { From 3d6f4edad8a6c128c36123014bcb34b271522cb9 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:15:08 +0100 Subject: [PATCH 33/98] Feature: Block Catalogue Search Function --- .../block-catalogue-modal.element.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index 1beec67023..0b0dd04710 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -6,6 +6,7 @@ import type { UmbBlockTypeWithGroupKey, } from '@umbraco-cms/backoffice/block'; import { css, html, customElement, state, repeat, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; +import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { UMB_MODAL_CONTEXT, UmbModalBaseElement, @@ -21,10 +22,13 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< private _groupedBlocks: Array<{ name?: string; blocks: Array }> = []; @state() - _openClipboard?: boolean; + private _openClipboard?: boolean; @state() - _workspacePath?: string; + private _workspacePath?: string; + + @state() + private _filter?: Array<{ name?: string; blocks: Array }>; constructor() { super(); @@ -85,8 +89,21 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< return html`Clipboard`; } + #onSearch(e: UUIInputEvent) { + console.log('on search change', e.target.value); + } + #renderCreateEmpty() { return html` + ${this._groupedBlocks.length > 7 + ? html` + + ` + : nothing} ${this._groupedBlocks.map( (group) => html` ${group.name ? html`

${group.name}

` : nothing} @@ -133,6 +150,13 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< static styles = [ css` + #search { + width: 100%; + align-items: center; + } + #search uui-icon { + padding-left: var(--uui-size-space-3); + } .blockGroup { display: grid; gap: 1rem; From 14308c64a275a7cf56dd33f54308021215f398fd Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:53:41 +0100 Subject: [PATCH 34/98] ensure all cultures are lowercased --- .../src/packages/core/variant/variant-id.class.ts | 2 +- .../detail/language-detail.server.data-source.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts index 39a8b85b12..36a3a4daa0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts @@ -20,7 +20,7 @@ export class UmbVariantId { public readonly schedule: { publishTime?: string | null; unpublishTime?: string | null } | null = null; constructor(variantData: variantObject) { - this.culture = (variantData.culture === UMB_INVARIANT_CULTURE ? null : variantData.culture) ?? null; + this.culture = (variantData.culture === UMB_INVARIANT_CULTURE ? null : variantData.culture?.toLowerCase()) ?? null; this.segment = variantData.segment ?? null; this.schedule = variantData.schedule ?? null; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/repository/detail/language-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/language/repository/detail/language-detail.server.data-source.ts index 4bcbf6ead2..48536e1ef3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/repository/detail/language-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/repository/detail/language-detail.server.data-source.ts @@ -65,11 +65,11 @@ export class UmbLanguageServerDataSource implements UmbDetailDataSource Date: Thu, 22 Feb 2024 16:54:00 +0100 Subject: [PATCH 35/98] add missing languages as "variants" to document details --- .../document-detail.server.data-source.ts | 57 ++++++++++++++----- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts index 38ece37f81..fb1e73ead7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts @@ -1,4 +1,4 @@ -import type { UmbDocumentDetailModel } from '../../types.js'; +import type { UmbDocumentDetailModel, UmbDocumentVariantModel } from '../../types.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; @@ -9,6 +9,7 @@ import type { import { DocumentResource } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; +import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; /** * A data source for the Document that fetches data from the server @@ -18,6 +19,7 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; */ export class UmbDocumentServerDataSource implements UmbDetailDataSource { #host: UmbControllerHost; + #languageRepository; /** * Creates an instance of UmbDocumentServerDataSource. @@ -26,6 +28,7 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource 1 || document.variants[0].culture !== null; + if (!allowVariants) return { data: document }; + + const missingLanguages = languages.items.filter( + (language) => !document.variants.some((variant) => variant.culture === language.unique), + ); + document.variants = document.variants.concat( + missingLanguages.map((language) => { + const scaffold = this.createVariantScaffold(); + return { + ...scaffold, + languageName: language.name, + culture: language.unique, + isMandatory: language.isMandatory, + }; + }), + ); + return { data: document }; } From 056f578bbcc64d2617e9a42ea3d3dd60e83c9968 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:54:21 +0100 Subject: [PATCH 36/98] optimise variant-selector to use the language repository to find the name of languages --- .../variant-selector.element.ts | 71 +++++++++++-------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index 19f614d289..97b7c9e175 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -5,20 +5,13 @@ import { UUIInputEvent, type UUIPopoverContainerElement, } from '@umbraco-cms/backoffice/external/uui'; -import { - css, - html, - nothing, - customElement, - property, - state, - ifDefined, - query, -} from '@umbraco-cms/backoffice/external/lit'; +import { css, html, nothing, customElement, state, ifDefined, query } from '@umbraco-cms/backoffice/external/lit'; import { UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, type ActiveVariant } from '@umbraco-cms/backoffice/workspace'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbDocumentVariantModel } from '@umbraco-cms/backoffice/document'; +import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; @customElement('umb-variant-selector') export class UmbVariantSelectorElement extends UmbLitElement { @@ -32,8 +25,8 @@ export class UmbVariantSelectorElement extends UmbLitElement { @state() _activeVariants: Array = []; - @property({ attribute: false }) - public get _activeVariantsCultures(): string[] { + @state() + get _activeVariantsCultures(): string[] { return this._activeVariants.map((el) => el.culture ?? '') ?? []; } @@ -43,20 +36,30 @@ export class UmbVariantSelectorElement extends UmbLitElement { @state() private _name?: string; + private _languageName?: string | null; private _culture?: string | null; private _segment?: string | null; @state() - private _variantDisplayName?: string; + private get _variantDisplayName() { + return (this._languageName ? this._languageName : '') + (this._segment ? ' — ' + this._segment : ''); + } @state() - private _variantTitleName?: string; + private get _variantTitleName() { + return ( + (this._languageName ? this._languageName + ` (${this._culture})` : '') + + (this._segment ? ' — ' + this._segment : '') + ); + } @state() private _variantSelectorOpen = false; - // TODO: make adapt to backoffice locale. - private _cultureNames = new Intl.DisplayNames('en', { type: 'language' }); + #languageRepository = new UmbLanguageCollectionRepository(this); + + @state() + private _languages = new UmbArrayState([], (x) => x.unique); constructor() { super(); @@ -70,6 +73,15 @@ export class UmbVariantSelectorElement extends UmbLitElement { this.#variantContext = instance; this._observeVariantContext(); }); + + this._loadLanguages(); + } + + private async _loadLanguages() { + const { data: languages } = await this.#languageRepository.requestCollection({}); + if (!languages) return; + this._languages.setValue(languages.items); + this.requestUpdate('_languages'); } private async _observeVariants() { @@ -113,9 +125,17 @@ export class UmbVariantSelectorElement extends UmbLitElement { if (!this.#variantContext) return; const variantId = this.#variantContext.getVariantId(); + this._culture = variantId.culture; this._segment = variantId.segment; - this.updateVariantDisplayName(); + + this.observe( + this._languages.asObservable(), + (languages) => { + this._languageName = languages.find((language) => language.unique === variantId.culture)?.name ?? ''; + }, + '_languages', + ); this.observe( this.#variantContext.name, @@ -126,15 +146,6 @@ export class UmbVariantSelectorElement extends UmbLitElement { ); } - private updateVariantDisplayName() { - if (!this._culture && !this._segment) return; - this._variantTitleName = - (this._culture ? this._cultureNames.of(this._culture) + ` (${this._culture})` : '') + - (this._segment ? ' — ' + this._segment : ''); - this._variantDisplayName = - (this._culture ? this._cultureNames.of(this._culture) : '') + (this._segment ? ' — ' + this._segment : ''); - } - // TODO: find a way where we don't have to do this for all workspaces. private _handleInput(event: UUIInputEvent) { if (event instanceof UUIInputEvent) { @@ -189,7 +200,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { return html` ${ - this._variants && this._variants.length > 0 + this._variants?.length ? html` ${ - this._variants && this._variants.length > 0 + this._variants?.length ? html` ` : nothing}
- ${variant.name} (${variant.culture}) ${variant.segment} + ${variant.name || + this._languages.getValue().find((x) => x.unique === variant.culture)?.name} + (${variant.culture}) ${variant.segment}
${variant.state}
From 6900edb69d538f9f60ca9c2b035348085689a145 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:08:57 +0100 Subject: [PATCH 37/98] implement UmbTextStyles --- .../variant-selector/variant-selector.element.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index 97b7c9e175..bd49755bb1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -12,6 +12,7 @@ import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/back import type { UmbDocumentVariantModel } from '@umbraco-cms/backoffice/document'; import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @customElement('umb-variant-selector') export class UmbVariantSelectorElement extends UmbLitElement { @@ -273,6 +274,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { } static styles = [ + UmbTextStyles, css` #name-input { width: 100%; @@ -344,12 +346,6 @@ export class UmbVariantSelectorElement extends UmbLitElement { font-size: 14px; cursor: pointer; border-bottom: 1px solid var(--uui-color-divider-standalone); - font-family: - Lato, - Helvetica Neue, - Helvetica, - Arial, - sans-serif; } .variant-selector-switch-button:hover { From 5328a680e5c86e998d9542653cab7dc04f0bd6f0 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:09:10 +0100 Subject: [PATCH 38/98] move variant generation to the workspace --- .../document-detail.server.data-source.ts | 25 ---------- .../workspace/document-workspace.context.ts | 50 +++++++++++++++++-- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts index fb1e73ead7..85526b3f5f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts @@ -9,7 +9,6 @@ import type { import { DocumentResource } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; -import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; /** * A data source for the Document that fetches data from the server @@ -19,7 +18,6 @@ import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/languag */ export class UmbDocumentServerDataSource implements UmbDetailDataSource { #host: UmbControllerHost; - #languageRepository; /** * Creates an instance of UmbDocumentServerDataSource. @@ -28,7 +26,6 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource 1 || document.variants[0].culture !== null; - if (!allowVariants) return { data: document }; - - const missingLanguages = languages.items.filter( - (language) => !document.variants.some((variant) => variant.culture === language.unique), - ); - document.variants = document.variants.concat( - missingLanguages.map((language) => { - const scaffold = this.createVariantScaffold(); - return { - ...scaffold, - languageName: language.name, - culture: language.unique, - isMandatory: language.isMandatory, - }; - }), - ); - return { data: document }; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index fbb69e8984..a8af434a93 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -2,7 +2,7 @@ import { UmbDocumentTypeDetailRepository } from '../../document-types/repository import { UmbDocumentPropertyDataContext } from '../property-dataset-context/document-property-dataset-context.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; import { UmbDocumentDetailRepository } from '../repository/index.js'; -import type { UmbDocumentDetailModel } from '../types.js'; +import { UmbDocumentVariantState, type UmbDocumentDetailModel, type UmbDocumentVariantModel } from '../types.js'; import type { UmbDocumentVariantPickerModalData } from '../modals/index.js'; import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js'; import { UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT } from '../global-contexts/document-variant-manager.context.js'; @@ -15,8 +15,16 @@ import { type UmbVariantableWorkspaceContextInterface, type UmbPublishableWorkspaceContextInterface, } from '@umbraco-cms/backoffice/workspace'; -import { appendToFrozenArray, partialUpdateFrozenArray, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; +import { + appendToFrozenArray, + combineObservables, + partialUpdateFrozenArray, + UmbArrayState, + UmbObjectState, +} from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; type EntityType = UmbDocumentDetailModel; export class UmbDocumentWorkspaceContext @@ -32,6 +40,10 @@ export class UmbDocumentWorkspaceContext */ #currentData = new UmbObjectState(undefined); #getDataPromise?: Promise; + #variantManagerContext?: typeof UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT.TYPE; + #languageRepository = new UmbLanguageCollectionRepository(this); + #languageCollection = new UmbArrayState([], (x) => x.unique); + public isLoaded() { return this.#getDataPromise; } @@ -41,15 +53,35 @@ export class UmbDocumentWorkspaceContext readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.documentType.unique); readonly contentTypeHasCollection = this.#currentData.asObservablePart((data) => !!data?.documentType.collection); - readonly variants = this.#currentData.asObservablePart((data) => data?.variants || []); + readonly variants: Observable = combineObservables( + [this.#currentData.asObservablePart((data) => data?.variants ?? []), this.#languageCollection.asObservable()], + ([variants, languages]) => { + const missingLanguages = languages.filter( + (language) => !variants.some((variant) => variant.culture === language.unique), + ); + return variants.concat( + missingLanguages.map((language) => { + return { + name: '', + createDate: '', + publishDate: '', + updateDate: '', + state: UmbDocumentVariantState.NOT_CREATED, + segment: null, + culture: language.unique, + languageName: language.name, + isMandatory: language.isMandatory, + }; + }), + ); + }, + ); readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []); readonly templateId = this.#currentData.asObservablePart((data) => data?.template?.unique || null); readonly structure = new UmbContentTypePropertyStructureManager(this, new UmbDocumentTypeDetailRepository(this)); readonly splitView = new UmbWorkspaceSplitViewManager(); - #variantManagerContext?: typeof UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT.TYPE; - constructor(host: UmbControllerHost) { super(host, UMB_DOCUMENT_WORKSPACE_ALIAS); @@ -59,11 +91,19 @@ export class UmbDocumentWorkspaceContext this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique)); + this.loadLanguages(); + /* TODO: Make something to ensure all variants are present in data? Seems like a good idea?. */ } + async loadLanguages() { + const { data } = await this.#languageRepository.requestCollection({}); + const languages = data?.items || []; + this.#languageCollection.setValue(languages); + } + async load(unique: string) { this.#getDataPromise = this.repository.requestByUnique(unique); const { data } = await this.#getDataPromise; From 3aec7a1eaf72e7bbc77e3fac0b5e50b385eb7d4d Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:35:19 +0100 Subject: [PATCH 39/98] return variants --- .../workspace/document-workspace.context.ts | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index a8af434a93..d0d77a079a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -52,30 +52,7 @@ export class UmbDocumentWorkspaceContext readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.documentType.unique); readonly contentTypeHasCollection = this.#currentData.asObservablePart((data) => !!data?.documentType.collection); - - readonly variants: Observable = combineObservables( - [this.#currentData.asObservablePart((data) => data?.variants ?? []), this.#languageCollection.asObservable()], - ([variants, languages]) => { - const missingLanguages = languages.filter( - (language) => !variants.some((variant) => variant.culture === language.unique), - ); - return variants.concat( - missingLanguages.map((language) => { - return { - name: '', - createDate: '', - publishDate: '', - updateDate: '', - state: UmbDocumentVariantState.NOT_CREATED, - segment: null, - culture: language.unique, - languageName: language.name, - isMandatory: language.isMandatory, - }; - }), - ); - }, - ); + readonly variants = this.#currentData.asObservablePart((data) => data?.variants ?? []); readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []); readonly templateId = this.#currentData.asObservablePart((data) => data?.template?.unique || null); From 051a37f1611e731ab273943cf7cdb32068276831 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:45:38 +0100 Subject: [PATCH 40/98] use a new type for variant option to render the variant selector and optimise away some of the properties that isn't needed --- .../variant-selector.element.ts | 128 +++++++++++------- 1 file changed, 77 insertions(+), 51 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index bd49755bb1..1a0224b6e9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -11,8 +11,19 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbDocumentVariantModel } from '@umbraco-cms/backoffice/document'; import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbArrayState, combineObservables } from '@umbraco-cms/backoffice/observable-api'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; + +type UmbDocumentVariantOption = { + culture: string | null; + segment: string | null; + title: string; + displayName: string; + state: DocumentVariantStateModel; +}; + +type UmbDocumentVariantOptions = Array; @customElement('umb-variant-selector') export class UmbVariantSelectorElement extends UmbLitElement { @@ -20,7 +31,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { private _popoverElement?: UUIPopoverContainerElement; @state() - _variants: Array = []; + private _variants: UmbDocumentVariantOptions = []; // TODO: Stop using document context specific ActiveVariant type. @state() @@ -37,30 +48,17 @@ export class UmbVariantSelectorElement extends UmbLitElement { @state() private _name?: string; - private _languageName?: string | null; - private _culture?: string | null; - private _segment?: string | null; + @state() + private _variantDisplayName = ''; @state() - private get _variantDisplayName() { - return (this._languageName ? this._languageName : '') + (this._segment ? ' — ' + this._segment : ''); - } - - @state() - private get _variantTitleName() { - return ( - (this._languageName ? this._languageName + ` (${this._culture})` : '') + - (this._segment ? ' — ' + this._segment : '') - ); - } + private _variantTitleName = ''; @state() private _variantSelectorOpen = false; #languageRepository = new UmbLanguageCollectionRepository(this); - - @state() - private _languages = new UmbArrayState([], (x) => x.unique); + #languages = new UmbArrayState([], (x) => x.unique); constructor() { super(); @@ -81,28 +79,54 @@ export class UmbVariantSelectorElement extends UmbLitElement { private async _loadLanguages() { const { data: languages } = await this.#languageRepository.requestCollection({}); if (!languages) return; - this._languages.setValue(languages.items); - this.requestUpdate('_languages'); + this.#languages.setValue(languages.items); } private async _observeVariants() { if (!this.#splitViewContext) return; const workspaceContext = this.#splitViewContext.getWorkspaceContext(); - if (workspaceContext) { - this.observe( - workspaceContext.variants, - (variants) => { - if (variants) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - // TODO: figure out what we do with the different variant models. Document has a state, but the variant model does not. - this._variants = variants; - } - }, - '_observeVariants', - ); - } + if (!workspaceContext) throw new Error('Split View Workspace context not found'); + + const combinedVariantOptions: Observable = combineObservables( + [workspaceContext.variants, this.#languages.asObservable()], + ([variants, languages]) => { + const variantOptions: UmbDocumentVariantOptions = variants.map((variant) => { + const language = languages.find((lang) => lang.unique === variant.culture); + return { + culture: variant.culture, + segment: variant.segment, + title: + `${variant.name ?? language?.name ?? ''} (${variant.culture})` + + (variant.segment ? ` — ${variant.segment}` : ''), + displayName: (language ? language.name : '') + (variant.segment ? ` — ${variant.segment}` : ''), + state: (variant as UmbDocumentVariantModel).state ?? DocumentVariantStateModel.NOT_CREATED, + }; + }); + + const missingLanguages: UmbDocumentVariantOptions = languages + .filter((language) => !variants.some((variant) => variant.culture === language.unique)) + .map((language) => { + return { + culture: language.unique, + segment: null, + title: `${language.name} (${language.unique})`, + displayName: language.name, + state: DocumentVariantStateModel.NOT_CREATED, + }; + }); + + return [...variantOptions, ...missingLanguages]; + }, + ); + + this.observe( + combinedVariantOptions, + (variants) => { + this._variants = variants; + }, + '_observeVariants', + ); } private async _observeActiveVariants() { @@ -127,13 +151,16 @@ export class UmbVariantSelectorElement extends UmbLitElement { const variantId = this.#variantContext.getVariantId(); - this._culture = variantId.culture; - this._segment = variantId.segment; + const culture = variantId.culture; + const segment = variantId.segment; this.observe( - this._languages.asObservable(), + this.#languages.asObservable(), (languages) => { - this._languageName = languages.find((language) => language.unique === variantId.culture)?.name ?? ''; + const languageName = languages.find((language) => language.unique === variantId.culture)?.name ?? ''; + this._variantDisplayName = (languageName ? languageName : '') + (segment ? ' — ' + segment : ''); + this._variantTitleName = + (languageName ? `${languageName} (${culture})` : '') + (segment ? ' — ' + segment : ''); }, '_languages', ); @@ -162,11 +189,11 @@ export class UmbVariantSelectorElement extends UmbLitElement { } } - private _switchVariant(variant: UmbDocumentVariantModel) { + private _switchVariant(variant: UmbDocumentVariantOption) { this.#splitViewContext?.switchVariant(UmbVariantId.Create(variant)); } - private _openSplitView(variant: UmbDocumentVariantModel) { + private _openSplitView(variant: UmbDocumentVariantOption) { this.#splitViewContext?.openSplitView(UmbVariantId.Create(variant)); } @@ -174,12 +201,12 @@ export class UmbVariantSelectorElement extends UmbLitElement { this.#splitViewContext?.closeSplitView(); } - private _isVariantActive(culture: string) { - return this._activeVariantsCultures.includes(culture); + private _isVariantActive(culture: string | null) { + return culture !== null ? this._activeVariantsCultures.includes(culture) : true; } - private _isNotPublishedMode(culture: string, state: DocumentVariantStateModel) { - return state !== DocumentVariantStateModel.PUBLISHED && !this._isVariantActive(culture!); + private _isNotPublishedMode(culture: string | null, state: DocumentVariantStateModel) { + return state !== DocumentVariantStateModel.PUBLISHED && !this._isVariantActive(culture); } // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. @@ -235,22 +262,21 @@ export class UmbVariantSelectorElement extends UmbLitElement {
    ${this._variants.map( (variant) => html` -
  • +
  • - ${this._isVariantActive(variant.culture!) + ${this._isVariantActive(variant.culture) ? nothing : html` Date: Fri, 23 Feb 2024 12:01:48 +0100 Subject: [PATCH 41/98] add variantsWithLanguages in variantable contexts --- .../variant-selector.element.ts | 36 ++++--------------- ...workspace-variantable-context.interface.ts | 1 + .../document-workspace-editor.element.ts | 13 +++++-- .../workspace/document-workspace.context.ts | 19 ++++++++++ .../media-workspace-editor.element.ts | 2 +- .../workspace/media-workspace.context.ts | 35 ++++++++++++++++-- 6 files changed, 71 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index 1a0224b6e9..b145df3378 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -88,42 +88,18 @@ export class UmbVariantSelectorElement extends UmbLitElement { const workspaceContext = this.#splitViewContext.getWorkspaceContext(); if (!workspaceContext) throw new Error('Split View Workspace context not found'); - const combinedVariantOptions: Observable = combineObservables( - [workspaceContext.variants, this.#languages.asObservable()], - ([variants, languages]) => { - const variantOptions: UmbDocumentVariantOptions = variants.map((variant) => { - const language = languages.find((lang) => lang.unique === variant.culture); + this.observe( + workspaceContext.variantsWithLanguages, + (variants) => { + this._variants = variants.map((variant) => { return { culture: variant.culture, segment: variant.segment, - title: - `${variant.name ?? language?.name ?? ''} (${variant.culture})` + - (variant.segment ? ` — ${variant.segment}` : ''), - displayName: (language ? language.name : '') + (variant.segment ? ` — ${variant.segment}` : ''), + title: `${variant.name ?? ''} (${variant.culture})` + (variant.segment ? ` — ${variant.segment}` : ''), + displayName: variant.name + (variant.segment ? ` — ${variant.segment}` : ''), state: (variant as UmbDocumentVariantModel).state ?? DocumentVariantStateModel.NOT_CREATED, }; }); - - const missingLanguages: UmbDocumentVariantOptions = languages - .filter((language) => !variants.some((variant) => variant.culture === language.unique)) - .map((language) => { - return { - culture: language.unique, - segment: null, - title: `${language.name} (${language.unique})`, - displayName: language.name, - state: DocumentVariantStateModel.NOT_CREATED, - }; - }); - - return [...variantOptions, ...missingLanguages]; - }, - ); - - this.observe( - combinedVariantOptions, - (variants) => { - this._variants = variants; }, '_observeVariants', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts index 4e2004e5f2..f8701d1f95 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts @@ -12,6 +12,7 @@ export interface UmbVariantableWorkspaceContextInterface extends UmbSaveableWork // Variant: variants: Observable>; + variantsWithLanguages: Observable>; splitView: UmbWorkspaceSplitViewManager; getVariant(variantId: UmbVariantId): UmbVariantModel | undefined; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts index 49a5d76926..aee5bcccea 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts @@ -7,6 +7,7 @@ import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace'; import type { UmbRoute, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant'; +import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; @customElement('umb-document-workspace-editor') export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { @@ -26,6 +27,8 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { #workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; + #languageRepository = new UmbLanguageCollectionRepository(this); + constructor() { super(); @@ -39,7 +42,7 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { #observeVariants() { if (!this.#workspaceContext) return; this.observe( - this.#workspaceContext.variants, + this.#workspaceContext.variantsWithLanguages, (variants) => { this._availableVariants = variants; this._generateRoutes(); @@ -66,9 +69,15 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { this.#workspaceContext?.splitView.setActiveVariant(index, culture, segment); } - private _generateRoutes() { + private async _generateRoutes() { if (!this._availableVariants || this._availableVariants.length === 0) return; + const { data: languages } = await this.#languageRepository.requestCollection({}); + + if (languages?.items.length) { + this._availableVariants; + } + // Generate split view routes for all available routes const routes: Array = []; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index d0d77a079a..143bdda241 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -53,6 +53,25 @@ export class UmbDocumentWorkspaceContext readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.documentType.unique); readonly contentTypeHasCollection = this.#currentData.asObservablePart((data) => !!data?.documentType.collection); readonly variants = this.#currentData.asObservablePart((data) => data?.variants ?? []); + readonly variantsWithLanguages = combineObservables( + [this.variants, this.#languageCollection.asObservable()], + ([variants, languages]) => { + const missingLanguages = languages.filter((x) => !variants.some((v) => v.culture === x.unique)); + const newVariants = variants.concat( + missingLanguages.map((language) => ({ + state: UmbDocumentVariantState.NOT_CREATED, + isMandatory: language.isMandatory, + culture: language.unique, + segment: null, + name: '', + createDate: '', + publishDate: '', + updateDate: '', + })), + ); + return newVariants; + }, + ); readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []); readonly templateId = this.#currentData.asObservablePart((data) => data?.template?.unique || null); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts index 32f6b32487..77943a1cb5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts @@ -38,7 +38,7 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement { #observeVariants() { if (!this.#workspaceContext) return; this.observe( - this.#workspaceContext.variants, + this.#workspaceContext.variantsWithLanguages, (variants) => { this._availableVariants = variants; this._generateRoutes(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts index 9c87b9b8e7..3c3c345d28 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts @@ -3,15 +3,23 @@ import { UmbMediaPropertyDataContext } from '../property-dataset-context/media-p import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js'; import { UmbMediaDetailRepository } from '../repository/index.js'; import type { UmbMediaDetailModel } from '../types.js'; -import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { UmbVariantId, UmbVariantModel } from '@umbraco-cms/backoffice/variant'; import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; import { UmbEditableWorkspaceContextBase, UmbWorkspaceSplitViewManager, type UmbVariantableWorkspaceContextInterface, } from '@umbraco-cms/backoffice/workspace'; -import { appendToFrozenArray, partialUpdateFrozenArray, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; +import { + appendToFrozenArray, + combineObservables, + partialUpdateFrozenArray, + UmbArrayState, + UmbObjectState, +} from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { Observable } from 'rxjs'; +import { UmbLanguageCollectionRepository, UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; type EntityType = UmbMediaDetailModel; export class UmbMediaWorkspaceContext @@ -26,6 +34,8 @@ export class UmbMediaWorkspaceContext */ #currentData = new UmbObjectState(undefined); #getDataPromise?: Promise; + #languageRepository = new UmbLanguageCollectionRepository(this); + #languageCollection = new UmbArrayState([], (x) => x.unique); public isLoaded() { return this.#getDataPromise; } @@ -34,6 +44,22 @@ export class UmbMediaWorkspaceContext readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.mediaType.unique); readonly variants = this.#currentData.asObservablePart((data) => data?.variants || []); + readonly variantsWithLanguages = combineObservables( + [this.variants, this.#languageCollection.asObservable()], + ([variants, languages]) => { + const missingLanguages = languages.filter((x) => !variants.some((v) => v.culture === x.unique)); + const newVariants = variants.concat( + missingLanguages.map((x) => ({ + culture: x.unique, + segment: null, + name: x.name, + createDate: '', + updateDate: '', + })), + ); + return newVariants; + }, + ); readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []); readonly structure = new UmbContentTypePropertyStructureManager(this, new UmbMediaTypeDetailRepository(this)); @@ -46,6 +72,11 @@ export class UmbMediaWorkspaceContext this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique)); } + async loadLanguages() { + const { data } = await this.#languageRepository.requestCollection({}); + this.#languageCollection.setValue(data?.items ?? []); + } + async load(unique: string) { this.#getDataPromise = this.repository.requestByUnique(unique); const { data } = await this.#getDataPromise; From 4bedc17855d36390766fa99d7e89882fec18f771 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 12:02:12 +0100 Subject: [PATCH 42/98] update imports --- .../media/media/workspace/media-workspace.context.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts index 3c3c345d28..cb67a1f8aa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts @@ -3,7 +3,7 @@ import { UmbMediaPropertyDataContext } from '../property-dataset-context/media-p import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js'; import { UmbMediaDetailRepository } from '../repository/index.js'; import type { UmbMediaDetailModel } from '../types.js'; -import type { UmbVariantId, UmbVariantModel } from '@umbraco-cms/backoffice/variant'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; import { UmbEditableWorkspaceContextBase, @@ -18,8 +18,7 @@ import { UmbObjectState, } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { Observable } from 'rxjs'; -import { UmbLanguageCollectionRepository, UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; +import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; type EntityType = UmbMediaDetailModel; export class UmbMediaWorkspaceContext From 2f03b68bb0d2d99db34ae0978140fc91d37890a0 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 12:03:40 +0100 Subject: [PATCH 43/98] rename to allowedVariants --- .../components/variant-selector/variant-selector.element.ts | 2 +- .../workspace-variantable-context.interface.ts | 2 +- .../documents/workspace/document-workspace-editor.element.ts | 2 +- .../documents/documents/workspace/document-workspace.context.ts | 2 +- .../media/media/workspace/media-workspace-editor.element.ts | 2 +- .../packages/media/media/workspace/media-workspace.context.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index b145df3378..2972360e36 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -89,7 +89,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { if (!workspaceContext) throw new Error('Split View Workspace context not found'); this.observe( - workspaceContext.variantsWithLanguages, + workspaceContext.allowedVariants, (variants) => { this._variants = variants.map((variant) => { return { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts index f8701d1f95..a40153c268 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts @@ -12,7 +12,7 @@ export interface UmbVariantableWorkspaceContextInterface extends UmbSaveableWork // Variant: variants: Observable>; - variantsWithLanguages: Observable>; + allowedVariants: Observable>; splitView: UmbWorkspaceSplitViewManager; getVariant(variantId: UmbVariantId): UmbVariantModel | undefined; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts index aee5bcccea..172501333f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts @@ -42,7 +42,7 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { #observeVariants() { if (!this.#workspaceContext) return; this.observe( - this.#workspaceContext.variantsWithLanguages, + this.#workspaceContext.allowedVariants, (variants) => { this._availableVariants = variants; this._generateRoutes(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 143bdda241..c6ab7b4e26 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -53,7 +53,7 @@ export class UmbDocumentWorkspaceContext readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.documentType.unique); readonly contentTypeHasCollection = this.#currentData.asObservablePart((data) => !!data?.documentType.collection); readonly variants = this.#currentData.asObservablePart((data) => data?.variants ?? []); - readonly variantsWithLanguages = combineObservables( + readonly allowedVariants = combineObservables( [this.variants, this.#languageCollection.asObservable()], ([variants, languages]) => { const missingLanguages = languages.filter((x) => !variants.some((v) => v.culture === x.unique)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts index 77943a1cb5..d8fba68018 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts @@ -38,7 +38,7 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement { #observeVariants() { if (!this.#workspaceContext) return; this.observe( - this.#workspaceContext.variantsWithLanguages, + this.#workspaceContext.allowedVariants, (variants) => { this._availableVariants = variants; this._generateRoutes(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts index cb67a1f8aa..f65aa898f2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts @@ -43,7 +43,7 @@ export class UmbMediaWorkspaceContext readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.mediaType.unique); readonly variants = this.#currentData.asObservablePart((data) => data?.variants || []); - readonly variantsWithLanguages = combineObservables( + readonly allowedVariants = combineObservables( [this.variants, this.#languageCollection.asObservable()], ([variants, languages]) => { const missingLanguages = languages.filter((x) => !variants.some((v) => v.culture === x.unique)); From b08f2b5a7eac98888a0657859384e58d36e0243a Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 12:05:55 +0100 Subject: [PATCH 44/98] update the name of the language variant --- .../components/variant-selector/variant-selector.element.ts | 2 +- .../documents/documents/workspace/document-workspace.context.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index 2972360e36..1a8f31c623 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -95,7 +95,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { return { culture: variant.culture, segment: variant.segment, - title: `${variant.name ?? ''} (${variant.culture})` + (variant.segment ? ` — ${variant.segment}` : ''), + title: variant.name + (variant.segment ? ` — ${variant.segment}` : ''), displayName: variant.name + (variant.segment ? ` — ${variant.segment}` : ''), state: (variant as UmbDocumentVariantModel).state ?? DocumentVariantStateModel.NOT_CREATED, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index c6ab7b4e26..9c817a92d4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -63,7 +63,7 @@ export class UmbDocumentWorkspaceContext isMandatory: language.isMandatory, culture: language.unique, segment: null, - name: '', + name: language.name, createDate: '', publishDate: '', updateDate: '', From 6da51593b1b9b6df9dbba14f639a4a46053a950b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 12:07:07 +0100 Subject: [PATCH 45/98] request same amount of languages --- .../packages/language/global-contexts/app-language.context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/global-contexts/app-language.context.ts b/src/Umbraco.Web.UI.Client/src/packages/language/global-contexts/app-language.context.ts index ce2ca1abec..eb077dcfe5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/global-contexts/app-language.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/global-contexts/app-language.context.ts @@ -26,7 +26,7 @@ export class UmbAppLanguageContext extends UmbBaseController implements UmbApi { } async #observeLanguages() { - const { data } = await this.#languageCollectionRepository.requestCollection({ skip: 0, take: 100 }); + const { data } = await this.#languageCollectionRepository.requestCollection({}); // TODO: make this observable / update when languages are added/removed/updated if (data) { From 89e6ae062adb30fe2deed6e98f2b6341c5ec582a Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 12:09:08 +0100 Subject: [PATCH 46/98] remove todo --- .../documents/workspace/document-workspace.context.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 9c817a92d4..3867c1f934 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -88,10 +88,6 @@ export class UmbDocumentWorkspaceContext this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique)); this.loadLanguages(); - - /* - TODO: Make something to ensure all variants are present in data? Seems like a good idea?. - */ } async loadLanguages() { From dcfb86733fc4f1e1b15c25ab6cb3adbc2446413c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:35:16 +0100 Subject: [PATCH 47/98] ensure culture and segment always falls back to null --- .../core/workspace/workspace-split-view-manager.class.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts index b7907e9277..b6b1490045 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts @@ -25,7 +25,7 @@ export class UmbWorkspaceSplitViewManager { } setActiveVariant(index: number, culture: string | null, segment: string | null) { - this.#activeVariantsInfo.appendOne({ index, culture, segment }); + this.#activeVariantsInfo.appendOne({ index, culture: culture || null, segment: segment || null }); } getActiveVariants() { From b4bb92a6e8ffba3b667f05afdeae4c0524adab38 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:36:11 +0100 Subject: [PATCH 48/98] allow to pick multiple variants --- .../document-variant-manager.context.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.ts index d0cababfed..5a847afd50 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.ts @@ -49,7 +49,7 @@ export class UmbDocumentVariantManagerContext async pickVariants( availableVariants: Array, type: UmbDocumentVariantPickerModalData['type'], - activeVariantCulture?: string, + activeVariantCultures?: Array, ): Promise { // If there is only one variant, we don't need to select anything. if (availableVariants.length === 1) { @@ -65,7 +65,7 @@ export class UmbDocumentVariantManagerContext const modalContext = this.#modalManagerContext.open(UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, { data: modalData, - value: { selection: activeVariantCulture ? [activeVariantCulture] : [] }, + value: { selection: activeVariantCultures ?? [] }, }); const result = await modalContext.onSubmit().catch(() => undefined); @@ -89,7 +89,11 @@ export class UmbDocumentVariantManagerContext async publish(documentUnique: string) { const { data } = await this.#documentRepository.requestByUnique(documentUnique); if (!data) throw new Error('Document not found'); - const variantIds = await this.pickVariants(data.variants, 'publish', this.#appLanguageCulture); + const variantIds = await this.pickVariants( + data.variants, + 'publish', + this.#appLanguageCulture ? [this.#appLanguageCulture] : undefined, + ); if (variantIds.length) { await this.#publishingRepository.publish(documentUnique, variantIds); } @@ -106,7 +110,11 @@ export class UmbDocumentVariantManagerContext // Only show published variants const variants = data.variants.filter((variant) => variant.state === UmbDocumentVariantState.PUBLISHED); - const variantIds = await this.pickVariants(variants, 'unpublish', this.#appLanguageCulture); + const variantIds = await this.pickVariants( + variants, + 'unpublish', + this.#appLanguageCulture ? [this.#appLanguageCulture] : undefined, + ); if (variantIds.length) { await this.#publishingRepository.unpublish(documentUnique, variantIds); From 64fa19f02dec26cc9c7e110d397df5696c2ab7fb Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:37:04 +0100 Subject: [PATCH 49/98] when saving/publishing, take into consideration the active variant, the changed variants, and find missing variants in the `allowedVariants` array --- .../workspace/document-workspace.context.ts | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 3867c1f934..3124a9fe6d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -7,7 +7,7 @@ import type { UmbDocumentVariantPickerModalData } from '../modals/index.js'; import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js'; import { UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT } from '../global-contexts/document-variant-manager.context.js'; import { UMB_DOCUMENT_WORKSPACE_ALIAS } from './manifests.js'; -import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; import { UmbEditableWorkspaceContextBase, @@ -24,7 +24,7 @@ import { } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; type EntityType = UmbDocumentDetailModel; export class UmbDocumentWorkspaceContext @@ -72,6 +72,7 @@ export class UmbDocumentWorkspaceContext return newVariants; }, ); + readonly changedVariants = new UmbArrayState([], (x) => x.compare); readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []); readonly templateId = this.#currentData.asObservablePart((data) => data?.template?.unique || null); @@ -213,6 +214,7 @@ export class UmbDocumentWorkspaceContext (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), ); this.#currentData.update({ values }); + this.changedVariants.appendOne(variantId); } } @@ -225,11 +227,45 @@ export class UmbDocumentWorkspaceContext const activeVariants = this.splitView.getActiveVariants(); const activeVariant = activeVariants.length ? activeVariants[0] : undefined; - const selectedVariants = await this.#variantManagerContext.pickVariants( - data.variants, // TODO: Add a filter function to only show variants that have been changed - type, - activeVariant?.culture ?? undefined, - ); + // Calculate variants + const pickedVariants: string[] = []; + const availableVariants = await firstValueFrom(this.allowedVariants); + let allowedVariants = data.variants; + + // Make sure that the active variant is in the allowed variants + if (activeVariant) { + const activeVariantInAvailableVariants = availableVariants.find( + (x) => x.culture === activeVariant.culture && x.segment === activeVariant.segment, + ); + if (activeVariantInAvailableVariants) { + pickedVariants.push(activeVariantInAvailableVariants.culture!); + allowedVariants = appendToFrozenArray( + allowedVariants, + activeVariantInAvailableVariants, + (x) => + x.culture === activeVariantInAvailableVariants.culture && + x.segment === activeVariantInAvailableVariants.segment, + ); + } + } + + // Make sure the changed variants are in the allowed variants + const changedVariants = this.changedVariants.getValue(); + if (changedVariants.length) { + pickedVariants.push(...changedVariants.map((x) => x.culture!)); + const changedVariantsInAvailableVariants = availableVariants.filter((x) => + changedVariants.some((y) => y.equal(new UmbVariantId(x))), + ); + for (const changedVariant of changedVariantsInAvailableVariants) { + allowedVariants = appendToFrozenArray( + allowedVariants, + changedVariant, + (x) => changedVariant.culture === x.culture && changedVariant.segment === x.segment, + ); + } + } + + const selectedVariants = await this.#variantManagerContext.pickVariants(allowedVariants, type, pickedVariants); // If no variants are selected, we don't save anything. if (!selectedVariants.length) return []; From 080474427119aca5a661500b4dffeb267c61ceb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 13:40:11 +0100 Subject: [PATCH 50/98] rename to dataset --- .../variant-selector.element.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index 1a8f31c623..91ba770e4e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -43,7 +43,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { } #splitViewContext?: typeof UMB_WORKSPACE_SPLIT_VIEW_CONTEXT.TYPE; - #variantContext?: typeof UMB_PROPERTY_DATASET_CONTEXT.TYPE; + #datasetContext?: typeof UMB_PROPERTY_DATASET_CONTEXT.TYPE; @state() private _name?: string; @@ -69,14 +69,14 @@ export class UmbVariantSelectorElement extends UmbLitElement { this._observeActiveVariants(); }); this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (instance) => { - this.#variantContext = instance; - this._observeVariantContext(); + this.#datasetContext = instance; + this.#observeDatasetContext(); }); - this._loadLanguages(); + this.#loadLanguages(); } - private async _loadLanguages() { + async #loadLanguages() { const { data: languages } = await this.#languageRepository.requestCollection({}); if (!languages) return; this.#languages.setValue(languages.items); @@ -122,10 +122,10 @@ export class UmbVariantSelectorElement extends UmbLitElement { } } - private async _observeVariantContext() { - if (!this.#variantContext) return; + async #observeDatasetContext() { + if (!this.#datasetContext) return; - const variantId = this.#variantContext.getVariantId(); + const variantId = this.#datasetContext.getVariantId(); const culture = variantId.culture; const segment = variantId.segment; @@ -142,7 +142,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { ); this.observe( - this.#variantContext.name, + this.#datasetContext.name, (name) => { this._name = name; }, @@ -157,10 +157,10 @@ export class UmbVariantSelectorElement extends UmbLitElement { if ( typeof target?.value === 'string' && - this.#variantContext && - isNameablePropertyDatasetContext(this.#variantContext) + this.#datasetContext && + isNameablePropertyDatasetContext(this.#datasetContext) ) { - this.#variantContext.setName(target.value); + this.#datasetContext.setName(target.value); } } } From e9582c45c5b36becf968faedffef3cf4c3cc70fa Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:41:09 +0100 Subject: [PATCH 51/98] set search --- .../block-catalogue-modal.element.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index 63dbb35be9..86979b23b2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -24,10 +24,10 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< private _workspacePath?: string; @state() - private _filtered?: Array<{ name?: string; blocks: Array }>; + private _filtered: Array<{ name?: string; blocks: Array }> = []; @state() - private _search?: string; + private _search = ''; constructor() { super(); @@ -66,6 +66,23 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< })); this._groupedBlocks = [{ blocks: noGroupBlocks }, ...grouped]; + this.#onFilter(); + } + + #onFilter() { + if (this._search.length < 3) { + this._filtered = this._groupedBlocks; + } else { + const search = this._search.toLowerCase(); + this._filtered = this._groupedBlocks.filter((group) => { + return group.blocks.find((block) => block.label?.toLocaleLowerCase().includes(search)) ? true : false; + }); + } + } + + #onSearch(e: UUIInputEvent) { + this._search = e.target.value as string; + this.#onFilter(); } render() { @@ -88,10 +105,6 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< return html`Clipboard`; } - #onSearch(e: UUIInputEvent) { - console.log('on search change', e.target.value); - } - #renderCreateEmpty() { return html` ${this._groupedBlocks.length > 7 From ff1f640eb70f75e91b33c56ebea31ccf379e3ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 13:44:37 +0100 Subject: [PATCH 52/98] privatize more --- .../variant-selector.element.ts | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index 91ba770e4e..cd2c99c25f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -65,8 +65,8 @@ export class UmbVariantSelectorElement extends UmbLitElement { this.consumeContext(UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, (instance) => { this.#splitViewContext = instance; - this._observeVariants(); - this._observeActiveVariants(); + this.#observeVariants(); + this.#observeActiveVariants(); }); this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (instance) => { this.#datasetContext = instance; @@ -82,7 +82,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { this.#languages.setValue(languages.items); } - private async _observeVariants() { + async #observeVariants() { if (!this.#splitViewContext) return; const workspaceContext = this.#splitViewContext.getWorkspaceContext(); @@ -105,7 +105,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { ); } - private async _observeActiveVariants() { + async #observeActiveVariants() { if (!this.#splitViewContext) return; const workspaceContext = this.#splitViewContext.getWorkspaceContext(); @@ -150,8 +150,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { ); } - // TODO: find a way where we don't have to do this for all workspaces. - private _handleInput(event: UUIInputEvent) { + #handleInput(event: UUIInputEvent) { if (event instanceof UUIInputEvent) { const target = event.composedPath()[0] as UUIInputElement; @@ -165,24 +164,24 @@ export class UmbVariantSelectorElement extends UmbLitElement { } } - private _switchVariant(variant: UmbDocumentVariantOption) { + #switchVariant(variant: UmbDocumentVariantOption) { this.#splitViewContext?.switchVariant(UmbVariantId.Create(variant)); } - private _openSplitView(variant: UmbDocumentVariantOption) { + #openSplitView(variant: UmbDocumentVariantOption) { this.#splitViewContext?.openSplitView(UmbVariantId.Create(variant)); } - private _closeSplitView() { + #closeSplitView() { this.#splitViewContext?.closeSplitView(); } - private _isVariantActive(culture: string | null) { + #isVariantActive(culture: string | null) { return culture !== null ? this._activeVariantsCultures.includes(culture) : true; } - private _isNotPublishedMode(culture: string | null, state: DocumentVariantStateModel) { - return state !== DocumentVariantStateModel.PUBLISHED && !this._isVariantActive(culture); + #isNotPublishedMode(culture: string | null, state: DocumentVariantStateModel) { + return state !== DocumentVariantStateModel.PUBLISHED && !this.#isVariantActive(culture); } // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. @@ -197,12 +196,13 @@ export class UmbVariantSelectorElement extends UmbLitElement { if (!isOpen) return; const host = this.getBoundingClientRect(); + // TODO: Ideally this is kept updated while open, but for now we just set it once: this._popoverElement.style.width = `${host.width}px`; } render() { return html` - + ${ this._variants?.length ? html` @@ -216,7 +216,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { ${this._activeVariants.length > 1 ? html` - + ` @@ -238,12 +238,12 @@ export class UmbVariantSelectorElement extends UmbLitElement {
      ${this._variants.map( (variant) => html` -
    • +
    • - ${this._isVariantActive(variant.culture) + ${this.#isVariantActive(variant.culture) ? nothing : html` this._openSplitView(variant)}> + @click=${() => this.#openSplitView(variant)}> Split view `} From e41983e876a3aad1b090f3154edad5277e3d5cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 13:48:43 +0100 Subject: [PATCH 53/98] use asObservablePart to ensure as few updates as possible --- .../variant-selector/variant-selector.element.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index cd2c99c25f..daf73d6c26 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -131,9 +131,10 @@ export class UmbVariantSelectorElement extends UmbLitElement { const segment = variantId.segment; this.observe( - this.#languages.asObservable(), - (languages) => { - const languageName = languages.find((language) => language.unique === variantId.culture)?.name ?? ''; + this.#languages.asObservablePart( + (languages) => languages.find((language) => language.unique === variantId.culture)?.name ?? '', + ), + (languageName) => { this._variantDisplayName = (languageName ? languageName : '') + (segment ? ' — ' + segment : ''); this._variantTitleName = (languageName ? `${languageName} (${culture})` : '') + (segment ? ' — ' + segment : ''); From ffc24ff2a53c67352d4198ac84d61d4e19a9b8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 13:51:34 +0100 Subject: [PATCH 54/98] fix condition --- .../conditions/document-workspace-has-collection.condition.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/conditions/document-workspace-has-collection.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/conditions/document-workspace-has-collection.condition.ts index e78b595b17..8e38df919a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/conditions/document-workspace-has-collection.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/conditions/document-workspace-has-collection.condition.ts @@ -19,9 +19,9 @@ export class UmbDocumentWorkspaceHasCollectionCondition extends UmbBaseControlle this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => { this.observe( - context.contentTypeCollection, + context.contentTypeHasCollection, (collection) => { - this.permitted = !!collection?.id; + this.permitted = collection; this.#onChange(); }, 'observeCollection', From ad0227292a8ef50ced82537b66b84b57ee46ef19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 14:05:51 +0100 Subject: [PATCH 55/98] no need for ifDefined here --- .../components/variant-selector/variant-selector.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index daf73d6c26..d04c0497a2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -211,7 +211,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { id="variant-selector-toggle" slot="append" popovertarget="variant-selector-popover" - title=${ifDefined(this._variantTitleName)}> + title=${this._variantTitleName}> ${this._variantDisplayName} From 53a0e46990cb556b607ee87713dd071c01b82bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 14:10:01 +0100 Subject: [PATCH 56/98] check for permittedExts to prevent issue --- .../controller/base-extensions-initializer.controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts index bd0846aebf..5600fdbc44 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts @@ -130,6 +130,8 @@ export abstract class UmbBaseExtensionsInitializer< #notifyChange = () => { this.#changeDebounce = undefined; + // This means that we have been destroyed: + if (this.#permittedExts === undefined) return; // The final list of permitted extensions to be displayed, this will be stripped from extensions that are overwritten by another extension and sorted accordingly. this.#exposedPermittedExts = [...this.#permittedExts]; From 564436292e2114e29fe9061329cc7a627f00b433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 14:16:12 +0100 Subject: [PATCH 57/98] prepare editor hooking into language observable --- .../workspace/document-workspace.context.ts | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 3124a9fe6d..8decc256d8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -42,7 +42,8 @@ export class UmbDocumentWorkspaceContext #getDataPromise?: Promise; #variantManagerContext?: typeof UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT.TYPE; #languageRepository = new UmbLanguageCollectionRepository(this); - #languageCollection = new UmbArrayState([], (x) => x.unique); + #languages = new UmbArrayState([], (x) => x.unique); + public readonly languages = this.#languages.asObservable(); public isLoaded() { return this.#getDataPromise; @@ -53,25 +54,22 @@ export class UmbDocumentWorkspaceContext readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.documentType.unique); readonly contentTypeHasCollection = this.#currentData.asObservablePart((data) => !!data?.documentType.collection); readonly variants = this.#currentData.asObservablePart((data) => data?.variants ?? []); - readonly allowedVariants = combineObservables( - [this.variants, this.#languageCollection.asObservable()], - ([variants, languages]) => { - const missingLanguages = languages.filter((x) => !variants.some((v) => v.culture === x.unique)); - const newVariants = variants.concat( - missingLanguages.map((language) => ({ - state: UmbDocumentVariantState.NOT_CREATED, - isMandatory: language.isMandatory, - culture: language.unique, - segment: null, - name: language.name, - createDate: '', - publishDate: '', - updateDate: '', - })), - ); - return newVariants; - }, - ); + readonly allowedVariants = combineObservables([this.variants, this.languages], ([variants, languages]) => { + const missingLanguages = languages.filter((x) => !variants.some((v) => v.culture === x.unique)); + const newVariants = variants.concat( + missingLanguages.map((language) => ({ + state: UmbDocumentVariantState.NOT_CREATED, + isMandatory: language.isMandatory, + culture: language.unique, + segment: null, + name: language.name, + createDate: '', + publishDate: '', + updateDate: '', + })), + ); + return newVariants; + }); readonly changedVariants = new UmbArrayState([], (x) => x.compare); readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []); readonly templateId = this.#currentData.asObservablePart((data) => data?.template?.unique || null); @@ -93,8 +91,7 @@ export class UmbDocumentWorkspaceContext async loadLanguages() { const { data } = await this.#languageRepository.requestCollection({}); - const languages = data?.items || []; - this.#languageCollection.setValue(languages); + this.#languages.setValue(data?.items ?? []); } async load(unique: string) { From ce305bd17d45875ddc047aba61001644f4f7d921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 14:16:17 +0100 Subject: [PATCH 58/98] note for editor --- .../documents/workspace/document-workspace-editor.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts index 172501333f..010308ee9d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts @@ -42,6 +42,7 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { #observeVariants() { if (!this.#workspaceContext) return; this.observe( + // Merge with languages here.. this.#workspaceContext.allowedVariants, (variants) => { this._availableVariants = variants; From 3ae62d288686d3f798705928e034f82a590a5cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 14:40:52 +0100 Subject: [PATCH 59/98] clean up --- .../workspace/document-workspace-editor.element.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts index 010308ee9d..f623b6011c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts @@ -8,6 +8,7 @@ import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace'; import type { UmbRoute, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant'; import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; +import { combineLatest } from '@umbraco-cms/backoffice/external/rxjs'; @customElement('umb-document-workspace-editor') export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { @@ -27,8 +28,6 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { #workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; - #languageRepository = new UmbLanguageCollectionRepository(this); - constructor() { super(); @@ -42,7 +41,6 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { #observeVariants() { if (!this.#workspaceContext) return; this.observe( - // Merge with languages here.. this.#workspaceContext.allowedVariants, (variants) => { this._availableVariants = variants; @@ -73,12 +71,6 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { private async _generateRoutes() { if (!this._availableVariants || this._availableVariants.length === 0) return; - const { data: languages } = await this.#languageRepository.requestCollection({}); - - if (languages?.items.length) { - this._availableVariants; - } - // Generate split view routes for all available routes const routes: Array = []; From 30097b98a2cc52497ccadbe5c75f3b00eb25c328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 14:41:19 +0100 Subject: [PATCH 60/98] prepare for new internal variants model --- .../workspace/document-workspace.context.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 8decc256d8..0c30839953 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -57,16 +57,19 @@ export class UmbDocumentWorkspaceContext readonly allowedVariants = combineObservables([this.variants, this.languages], ([variants, languages]) => { const missingLanguages = languages.filter((x) => !variants.some((v) => v.culture === x.unique)); const newVariants = variants.concat( - missingLanguages.map((language) => ({ - state: UmbDocumentVariantState.NOT_CREATED, - isMandatory: language.isMandatory, - culture: language.unique, - segment: null, - name: language.name, - createDate: '', - publishDate: '', - updateDate: '', - })), + missingLanguages.map( + (language) => + ({ + state: UmbDocumentVariantState.NOT_CREATED, + isMandatory: language.isMandatory, + culture: language.unique, + segment: null, + name: language.name, + createDate: null, + publishDate: null, + updateDate: null, + }) as UmbDocumentVariantModel, + ), ); return newVariants; }); From cacc4804a09d9b01ed03683753e588d5453d403a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 17:48:52 +0100 Subject: [PATCH 61/98] VariantId from string feature --- .../src/packages/core/variant/types.ts | 7 ++++ .../packages/core/variant/variant-id.class.ts | 36 ++++++++++++------- .../workspace-split-view-manager.class.ts | 2 +- .../document-publishing.server.data-source.ts | 3 +- .../media-workspace-editor.element.ts | 4 +-- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts index 2b5997280d..89b4a59249 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts @@ -1,3 +1,5 @@ +import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; + export interface UmbVariantModel { createDate: string | null; culture: string | null; @@ -5,3 +7,8 @@ export interface UmbVariantModel { segment: string | null; updateDate: string | null; } + +export interface UmbVariantOptionModel { + variant?: VariantType; + language: UmbLanguageDetailModel; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts index 36a3a4daa0..b2f9af347f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts @@ -1,32 +1,42 @@ -export type variantObject = { +export type UmbObjectWithVariantProperties = { culture: string | null; segment: string | null; - schedule?: { publishTime?: string | null; unpublishTime?: string | null }; }; +export function variantPropertiesObjectToString(variant: UmbObjectWithVariantProperties): string { + // Currently a direct copy of the toString method of variantId. + return (variant.culture || UMB_INVARIANT_CULTURE) + (variant.segment ? `_${variant.segment}` : ''); +} + export const UMB_INVARIANT_CULTURE = 'invariant'; export class UmbVariantId { - public static Create(variantData: variantObject): UmbVariantId { - return Object.freeze(new UmbVariantId(variantData)); + public static Create(variantData: UmbObjectWithVariantProperties): UmbVariantId { + return Object.freeze(new UmbVariantId(variantData.culture, variantData.segment)); } public static CreateInvariant(): UmbVariantId { - return Object.freeze(new UmbVariantId({ culture: null, segment: null })); + return Object.freeze(new UmbVariantId(null, null)); + } + + public static FromString(str: string): UmbVariantId { + const split = str.split('_'); + const culture = split[0] === UMB_INVARIANT_CULTURE ? null : split[0]; + const segment = split[1] ?? null; + return Object.freeze(new UmbVariantId(segment, culture)); } public readonly culture: string | null = null; public readonly segment: string | null = null; public readonly schedule: { publishTime?: string | null; unpublishTime?: string | null } | null = null; - constructor(variantData: variantObject) { - this.culture = (variantData.culture === UMB_INVARIANT_CULTURE ? null : variantData.culture?.toLowerCase()) ?? null; - this.segment = variantData.segment ?? null; - this.schedule = variantData.schedule ?? null; + constructor(culture: string | null, segment: string | null) { + this.culture = (culture === UMB_INVARIANT_CULTURE ? null : culture?.toLowerCase()) ?? null; + this.segment = segment ?? null; } - public compare(obj: variantObject): boolean { - return this.equal(new UmbVariantId(obj)); + public compare(obj: UmbObjectWithVariantProperties): boolean { + return this.equal(new UmbVariantId(obj.culture, obj.segment)); } public equal(variantId: UmbVariantId): boolean { @@ -34,6 +44,7 @@ export class UmbVariantId { } public toString(): string { + // Currently a direct copy of the VariantPropertiesObjectToString method const. return (this.culture || UMB_INVARIANT_CULTURE) + (this.segment ? `_${this.segment}` : ''); } @@ -57,11 +68,12 @@ export class UmbVariantId { return this.culture === null && this.segment === null; } - public toObject(): variantObject { + public toObject(): UmbObjectWithVariantProperties { return { culture: this.culture, segment: this.segment }; } // TODO: needs localization option: + // TODO: Consider if this should be handled else where, it does not seem like the responsibility of this class, since it contains wordings: public toDifferencesString(variantId: UmbVariantId): string { let r = ''; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts index b6b1490045..a48efbfe20 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts @@ -53,7 +53,7 @@ export class UmbWorkspaceSplitViewManager { const newVariants = [...activeVariants]; newVariants[index] = { index, culture: variantId.culture, segment: variantId.segment }; - const variantPart: string = newVariants.map((v) => new UmbVariantId(v).toString()).join('_&_'); + const variantPart: string = newVariants.map((v) => UmbVariantId.Create(v).toString()).join('_&_'); history.pushState(null, '', `${workspaceRoute}/${variantPart}`); return true; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/document-publishing.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/document-publishing.server.data-source.ts index 9abc1b8274..e51124f109 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/document-publishing.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/document-publishing.server.data-source.ts @@ -40,7 +40,8 @@ export class UmbDocumentPublishingServerDataSource { (variant) => { return { culture: variant.isCultureInvariant() ? null : variant.toCultureString(), - schedule: variant.schedule, + // TODO: NO, this does not belong as part of the UmbVariantID, we need another way to parse that around: + //schedule: variant.schedule, }; }, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts index d8fba68018..e5cc052897 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts @@ -75,7 +75,7 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement { this._availableVariants.forEach((variantA) => { this._availableVariants.forEach((variantB) => { routes.push({ - path: new UmbVariantId(variantA).toString() + '_&_' + new UmbVariantId(variantB).toString(), + path: UmbVariantId.Create(variantA).toString() + '_&_' + UmbVariantId.Create(variantB).toString(), component: this.splitViewElement, setup: (_component, info) => { // Set split view/active info.. @@ -91,7 +91,7 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement { // Single view: this._availableVariants.forEach((variant) => { routes.push({ - path: new UmbVariantId(variant).toString(), + path: UmbVariantId.Create(variant).toString(), component: this.splitViewElement, setup: (_component, info) => { // cause we might come from a split-view, we need to reset index 1. From 0bb38b57290fe76d8efcce8b5456dc4ffa69d21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Feb 2024 19:29:33 +0100 Subject: [PATCH 62/98] refactor --- .../selection-manager/selection.manager.ts | 24 ++-- .../src/packages/core/variant/types.ts | 4 + .../packages/core/variant/variant-id.class.ts | 6 +- .../variant-selector.element.ts | 90 ++++++------ ...workspace-variantable-context.interface.ts | 7 +- .../workspace-split-view-manager.class.ts | 4 +- .../entity-actions/publish.action.ts | 17 +-- .../entity-actions/unpublish.action.ts | 17 +-- ... document-variant-manager.context.temp_ts} | 13 +- .../documents/global-contexts/index.ts | 1 - .../documents/global-contexts/manifests.ts | 10 -- .../packages/documents/documents/manifests.ts | 2 - .../document-variant-picker-modal.element.ts | 60 +++++--- .../document-variant-picker-modal.stories.ts | 61 ++++++--- .../document-variant-picker-modal.token.ts | 6 +- .../document-detail.server.data-source.ts | 7 +- .../src/packages/documents/documents/types.ts | 5 +- .../document-workspace-editor.element.ts | 72 ++++------ .../workspace/document-workspace.context.ts | 129 ++++++++---------- .../src/packages/media/media/types.ts | 4 +- .../media-workspace-editor.element.ts | 64 ++++----- .../workspace/media-workspace.context.ts | 35 +++-- 22 files changed, 301 insertions(+), 337 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/{document-variant-manager.context.ts => document-variant-manager.context.temp_ts} (93%) delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/index.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts index 9451be884d..4f1f5a9d49 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts @@ -8,11 +8,11 @@ import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observab * @export * @class UmbSelectionManager */ -export class UmbSelectionManager extends UmbBaseController { +export class UmbSelectionManager extends UmbBaseController { #selectable = new UmbBooleanState(false); public readonly selectable = this.#selectable.asObservable(); - #selection = new UmbArrayState(>[], (x) => x); + #selection = new UmbArrayState(>[], (x) => x); public readonly selection = this.#selection.asObservable(); #multiple = new UmbBooleanState(false); @@ -51,10 +51,10 @@ export class UmbSelectionManager extends UmbBaseController { /** * Sets the current selection. - * @param {Array} value + * @param {Array} value * @memberof UmbSelectionManager */ - public setSelection(value: Array) { + public setSelection(value: Array) { if (this.getSelectable() === false) return; if (value === undefined) throw new Error('Value cannot be undefined'); const newSelection = this.getMultiple() ? value : value.slice(0, 1); @@ -87,20 +87,20 @@ export class UmbSelectionManager extends UmbBaseController { /** * Toggles the given unique id in the current selection. - * @param {(string | null)} unique + * @param {(ValueType)} unique * @memberof UmbSelectionManager */ - public toggleSelect(unique: string | null) { + public toggleSelect(unique: ValueType) { if (this.getSelectable() === false) return; this.isSelected(unique) ? this.deselect(unique) : this.select(unique); } /** * Appends the given unique id to the current selection. - * @param {(string | null)} unique + * @param {(ValueType)} unique * @memberof UmbSelectionManager */ - public select(unique: string | null) { + public select(unique: ValueType) { if (this.getSelectable() === false) return; if (this.isSelected(unique)) return; const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique]; @@ -110,10 +110,10 @@ export class UmbSelectionManager extends UmbBaseController { /** * Removes the given unique id from the current selection. - * @param {(string | null)} unique + * @param {(ValueType)} unique * @memberof UmbSelectionManager */ - public deselect(unique: string | null) { + public deselect(unique: ValueType) { if (this.getSelectable() === false) return; const newSelection = this.getSelection().filter((x) => x !== unique); this.#selection.setValue(newSelection); @@ -122,11 +122,11 @@ export class UmbSelectionManager extends UmbBaseController { /** * Returns true if the given unique id is selected. - * @param {(string | null)} unique + * @param {(ValueType)} unique * @return {*} * @memberof UmbSelectionManager */ - public isSelected(unique: string | null) { + public isSelected(unique: ValueType) { return this.getSelection().includes(unique); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts index 89b4a59249..ee32d57e86 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts @@ -11,4 +11,8 @@ export interface UmbVariantModel { export interface UmbVariantOptionModel { variant?: VariantType; language: UmbLanguageDetailModel; + /** + * The unique identifier is a VariantId string. + */ + unique: string; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts index b2f9af347f..2e6144b1c3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts @@ -10,6 +10,10 @@ export function variantPropertiesObjectToString(variant: UmbObjectWithVariantPro export const UMB_INVARIANT_CULTURE = 'invariant'; +/** + * An identifier representing a Variant. This is at current state a culture and a segment. + * The identifier is not specific for ContentType Variants, but is used for many type of identification of a culture and a segment. One case is any property of a ContentType can be resolved into a VariantId depending on their structural settings such as Vary by Culture and Vary by Segmentation. + */ export class UmbVariantId { public static Create(variantData: UmbObjectWithVariantProperties): UmbVariantId { return Object.freeze(new UmbVariantId(variantData.culture, variantData.segment)); @@ -30,7 +34,7 @@ export class UmbVariantId { public readonly segment: string | null = null; public readonly schedule: { publishTime?: string | null; unpublishTime?: string | null } | null = null; - constructor(culture: string | null, segment: string | null) { + constructor(culture?: string | null, segment?: string | null) { this.culture = (culture === UMB_INVARIANT_CULTURE ? null : culture?.toLowerCase()) ?? null; this.segment = segment ?? null; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index d04c0497a2..1bc79d33fb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -5,15 +5,12 @@ import { UUIInputEvent, type UUIPopoverContainerElement, } from '@umbraco-cms/backoffice/external/uui'; -import { css, html, nothing, customElement, state, ifDefined, query } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, nothing, customElement, state, query } from '@umbraco-cms/backoffice/external/lit'; import { UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, type ActiveVariant } from '@umbraco-cms/backoffice/workspace'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; -import type { UmbDocumentVariantModel } from '@umbraco-cms/backoffice/document'; -import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import { UmbArrayState, combineObservables } from '@umbraco-cms/backoffice/observable-api'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import type { UmbDocumentWorkspaceContext } from '@umbraco-cms/backoffice/document'; type UmbDocumentVariantOption = { culture: string | null; @@ -38,9 +35,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { _activeVariants: Array = []; @state() - get _activeVariantsCultures(): string[] { - return this._activeVariants.map((el) => el.culture ?? '') ?? []; - } + _activeVariantsCultures: string[] = []; #splitViewContext?: typeof UMB_WORKSPACE_SPLIT_VIEW_CONTEXT.TYPE; #datasetContext?: typeof UMB_PROPERTY_DATASET_CONTEXT.TYPE; @@ -57,9 +52,6 @@ export class UmbVariantSelectorElement extends UmbLitElement { @state() private _variantSelectorOpen = false; - #languageRepository = new UmbLanguageCollectionRepository(this); - #languages = new UmbArrayState([], (x) => x.unique); - constructor() { super(); @@ -67,37 +59,36 @@ export class UmbVariantSelectorElement extends UmbLitElement { this.#splitViewContext = instance; this.#observeVariants(); this.#observeActiveVariants(); + this.#observeCurrentVariant(); }); this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (instance) => { this.#datasetContext = instance; this.#observeDatasetContext(); + this.#observeCurrentVariant(); }); - - this.#loadLanguages(); - } - - async #loadLanguages() { - const { data: languages } = await this.#languageRepository.requestCollection({}); - if (!languages) return; - this.#languages.setValue(languages.items); } async #observeVariants() { if (!this.#splitViewContext) return; - const workspaceContext = this.#splitViewContext.getWorkspaceContext(); + // NOTICE: This is dirty (the TypeScript casting), we can only accept doing this so far because we currently only use the Variant Selector on Document Workspace. [NL] + // This would need a refactor to enable the code below to work with different ContentTypes. Main problem here is the state, which is not generic for them all. [NL] + const workspaceContext = this.#splitViewContext.getWorkspaceContext() as UmbDocumentWorkspaceContext; if (!workspaceContext) throw new Error('Split View Workspace context not found'); this.observe( - workspaceContext.allowedVariants, - (variants) => { - this._variants = variants.map((variant) => { + workspaceContext.variantOptions, + (options) => { + this._variants = options.map((option) => { + const name = option.variant?.name ?? option.language.name; + const segment = option.variant?.segment ?? null; return { - culture: variant.culture, - segment: variant.segment, - title: variant.name + (variant.segment ? ` — ${variant.segment}` : ''), - displayName: variant.name + (variant.segment ? ` — ${variant.segment}` : ''), - state: (variant as UmbDocumentVariantModel).state ?? DocumentVariantStateModel.NOT_CREATED, + // Notice the option object has a unique property, but it's not used here. (Its equivalent to a UmbVariantId string) [NL] + culture: option.language.unique, + segment: segment, + title: name + (segment ? ` — ${segment}` : ''), + displayName: name + (segment ? ` — ${segment}` : ''), + state: option.variant?.state ?? DocumentVariantStateModel.NOT_CREATED, }; }); }, @@ -115,6 +106,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { (activeVariants) => { if (activeVariants) { this._activeVariants = activeVariants; + this._activeVariantsCultures = this._activeVariants.map((el) => el.culture ?? '') ?? []; } }, '_observeActiveVariants', @@ -124,24 +116,6 @@ export class UmbVariantSelectorElement extends UmbLitElement { async #observeDatasetContext() { if (!this.#datasetContext) return; - - const variantId = this.#datasetContext.getVariantId(); - - const culture = variantId.culture; - const segment = variantId.segment; - - this.observe( - this.#languages.asObservablePart( - (languages) => languages.find((language) => language.unique === variantId.culture)?.name ?? '', - ), - (languageName) => { - this._variantDisplayName = (languageName ? languageName : '') + (segment ? ' — ' + segment : ''); - this._variantTitleName = - (languageName ? `${languageName} (${culture})` : '') + (segment ? ' — ' + segment : ''); - }, - '_languages', - ); - this.observe( this.#datasetContext.name, (name) => { @@ -151,6 +125,30 @@ export class UmbVariantSelectorElement extends UmbLitElement { ); } + async #observeCurrentVariant() { + if (!this.#datasetContext || !this.#splitViewContext) return; + const workspaceContext = this.#splitViewContext.getWorkspaceContext(); + if (!workspaceContext) return; + + const variantId = this.#datasetContext.getVariantId(); + // Find the variant option matching this, to get the language name... + + const culture = variantId.culture; + const segment = variantId.segment; + + this.observe( + workspaceContext.variantOptions, + (options) => { + const option = options.find((option) => option.language.unique === culture); + const languageName = option?.language.name; + this._variantDisplayName = (languageName ? languageName : '') + (segment ? ` — ${segment}` : ''); + this._variantTitleName = + (languageName ? `${languageName} (${culture})` : '') + (segment ? ` — ${segment}` : ''); + }, + '_currentLanguage', + ); + } + #handleInput(event: UUIInputEvent) { if (event instanceof UUIInputEvent) { const target = event.composedPath()[0] as UUIInputElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts index a40153c268..341f6b897a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts @@ -2,17 +2,18 @@ import type { UmbWorkspaceSplitViewManager } from '../workspace-split-view-manag import type { UmbPropertyDatasetContext } from '../../property/property-dataset/property-dataset-context.interface.js'; import type { UmbSaveableWorkspaceContextInterface } from './saveable-workspace-context.interface.js'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import type { UmbVariantId, UmbVariantModel } from '@umbraco-cms/backoffice/variant'; +import type { UmbVariantId, UmbVariantModel, UmbVariantOptionModel } from '@umbraco-cms/backoffice/variant'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -export interface UmbVariantableWorkspaceContextInterface extends UmbSaveableWorkspaceContextInterface { +export interface UmbVariantableWorkspaceContextInterface + extends UmbSaveableWorkspaceContextInterface { // Name: getName(variantId?: UmbVariantId): string | undefined; setName(name: string, variantId?: UmbVariantId): void; // Variant: variants: Observable>; - allowedVariants: Observable>; + variantOptions: Observable>>; splitView: UmbWorkspaceSplitViewManager; getVariant(variantId: UmbVariantId): UmbVariantModel | undefined; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts index a48efbfe20..63bbf04d0a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view-manager.class.ts @@ -70,7 +70,7 @@ export class UmbWorkspaceSplitViewManager { const currentVariant = this.getActiveVariants()[0]; const workspaceRoute = this.getWorkspaceRoute(); if (currentVariant && workspaceRoute) { - history.pushState(null, '', `${workspaceRoute}/${new UmbVariantId(currentVariant)}_&_${newVariant.toString()}`); + history.pushState(null, '', `${workspaceRoute}/${UmbVariantId.Create(currentVariant)}_&_${newVariant}`); return true; } return false; @@ -83,7 +83,7 @@ export class UmbWorkspaceSplitViewManager { if (activeVariants && index < activeVariants.length) { const newVariants = activeVariants.filter((x) => x.index !== index); - const variantPart: string = newVariants.map((v) => new UmbVariantId(v).toString()).join('_&_'); + const variantPart: string = newVariants.map((v) => UmbVariantId.Create(v)).join('_&_'); history.pushState(null, '', `${workspaceRoute}/${variantPart}`); return true; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts index d7c794512d..0276e0f366 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts @@ -1,21 +1,10 @@ -import { UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT } from '../global-contexts/index.js'; import type { UmbDocumentPublishingRepository } from '../repository/index.js'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; export class UmbPublishDocumentEntityAction extends UmbEntityActionBase { - #variantManagerContext?: typeof UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT.TYPE; - - constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { - super(host, repositoryAlias, unique, entityType); - - this.consumeContext(UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT, (context) => { - this.#variantManagerContext = context; - }); - } - async execute() { - if (!this.#variantManagerContext) throw new Error('Variant manager context is missing'); - await this.#variantManagerContext.publish(this.unique); + throw new Error('This action not implemented.'); + //if (!this.#variantManagerContext) throw new Error('Variant manager context is missing'); + //await this.#variantManagerContext.publish(this.unique); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts index 978963da48..cf34e477e1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts @@ -1,21 +1,10 @@ -import { UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT } from '../global-contexts/index.js'; import type { UmbDocumentPublishingRepository } from '../repository/index.js'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase { - #variantManagerContext?: typeof UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT.TYPE; - - constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { - super(host, repositoryAlias, unique, entityType); - - this.consumeContext(UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT, (context) => { - this.#variantManagerContext = context; - }); - } - async execute() { - if (!this.#variantManagerContext) throw new Error('Variant manager context is missing'); - await this.#variantManagerContext.unpublish(this.unique); + throw new Error('This action not implemented.'); + //if (!this.#variantManagerContext) throw new Error('Variant manager context is missing'); + //await this.#variantManagerContext.unpublish(this.unique); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts similarity index 93% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts index 5a847afd50..d5323f8a55 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts @@ -1,4 +1,4 @@ -import { UmbDocumentVariantState, type UmbDocumentVariantModel } from '../types.js'; +import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../types.js'; import { UmbDocumentDetailRepository } from '../repository/detail/document-detail.repository.js'; import { UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, @@ -47,9 +47,9 @@ export class UmbDocumentVariantManagerContext * @returns The selected variants to perform the operation on. */ async pickVariants( - availableVariants: Array, type: UmbDocumentVariantPickerModalData['type'], - activeVariantCultures?: Array, + availableVariants: Array, + activeVariantCultures?: Array, ): Promise { // If there is only one variant, we don't need to select anything. if (availableVariants.length === 1) { @@ -60,7 +60,7 @@ export class UmbDocumentVariantManagerContext const modalData: UmbDocumentVariantPickerModalData = { type, - variants: availableVariants, + options: availableVariants, }; const modalContext = this.#modalManagerContext.open(UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, { @@ -75,6 +75,7 @@ export class UmbDocumentVariantManagerContext const selectedVariants = result.selection.map((x) => x?.toLowerCase() ?? ''); // Match the result to the available variants. + // Why would this be needed if the modal is only showing available variants? [NL] const variantIds = availableVariants .filter((x) => selectedVariants.includes(x.culture!)) .map((x) => UmbVariantId.Create(x)); @@ -90,8 +91,8 @@ export class UmbDocumentVariantManagerContext const { data } = await this.#documentRepository.requestByUnique(documentUnique); if (!data) throw new Error('Document not found'); const variantIds = await this.pickVariants( - data.variants, 'publish', + data.variants, this.#appLanguageCulture ? [this.#appLanguageCulture] : undefined, ); if (variantIds.length) { @@ -111,8 +112,8 @@ export class UmbDocumentVariantManagerContext const variants = data.variants.filter((variant) => variant.state === UmbDocumentVariantState.PUBLISHED); const variantIds = await this.pickVariants( - variants, 'unpublish', + variants, this.#appLanguageCulture ? [this.#appLanguageCulture] : undefined, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/index.ts deleted file mode 100644 index 8eed586555..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './document-variant-manager.context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/manifests.ts deleted file mode 100644 index 2b5b972ebb..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/manifests.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ManifestGlobalContext } from '@umbraco-cms/backoffice/extension-registry'; - -export const manifests: Array = [ - { - type: 'globalContext', - alias: 'Umb.GlobalContext.DocumentVariantManager', - name: 'Document Variant Manager Context', - js: () => import('./document-variant-manager.context.js'), - }, -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts index 857816321f..4b900ea615 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts @@ -12,7 +12,6 @@ import { manifests as modalManifests } from './modals/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as userPermissionManifests } from './user-permissions/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; -import { manifests as globalContextManifests } from './global-contexts/manifests.js'; export const manifests = [ ...breadcrumbManifests, @@ -29,5 +28,4 @@ export const manifests = [ ...treeManifests, ...userPermissionManifests, ...workspaceManifests, - ...globalContextManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts index 3e85234acd..7d60157b54 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts @@ -1,10 +1,10 @@ -import { type UmbDocumentVariantModel, UmbDocumentVariantState } from '../../types.js'; +import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../types.js'; import type { UmbDocumentVariantPickerModalValue, UmbDocumentVariantPickerModalData, } from './document-variant-picker-modal.token.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, customElement, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; @@ -13,7 +13,17 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement< UmbDocumentVariantPickerModalData, UmbDocumentVariantPickerModalValue > { - #selectionManager = new UmbSelectionManager(this); + #selectionManager = new UmbSelectionManager(this); + + @state() + _selection: Array = []; + + constructor() { + super(); + this.observe(this.#selectionManager.selection, (selection) => { + this._selection = selection; + }); + } connectedCallback(): void { super.connectedCallback(); @@ -21,16 +31,18 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement< this.#selectionManager.setMultiple(true); // Make sure all mandatory variants are selected when not in unpublish mode + // TODO: Currently only supports culture variants, not segment variants, but as well our Selection Manager also only supports a single string value pr. selection... [NL] this.#selectionManager.setSelection(this.value?.selection ?? []); + if (this.data?.type !== 'unpublish') { this.#selectMandatoryVariants(); } } #selectMandatoryVariants() { - this.data?.variants.forEach((variant) => { - if (variant.isMandatory) { - this.#selectionManager.select(variant.culture); + this.data?.options.forEach((variant) => { + if (variant.language?.isMandatory) { + this.#selectionManager.select(variant.unique); } }); } @@ -87,17 +99,17 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement< return html`

      ${this.localize.term(this.#subtitle)}

      ${repeat( - this.data?.variants ?? [], - (item) => item.culture, - (item) => html` + this.data?.options ?? [], + (option) => option.unique, + (option) => html` this.#selectionManager.select(item.culture)} - @deselected=${() => this.#selectionManager.deselect(item.culture)} - ?selected=${this.#selectionManager.isSelected(item.culture)}> + label=${option.variant?.name ?? option.language.name} + @selected=${() => this.#selectionManager.select(option.unique)} + @deselected=${() => this.#selectionManager.deselect(option.unique)} + ?selected=${this._selection.includes(option.language.unique)}> - ${this.#renderLabel(item)} + ${this.#renderLabel(option)} `, )} @@ -114,11 +126,14 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement<
      `; } - #renderLabel(variant: UmbDocumentVariantModel) { + #renderLabel(option: UmbDocumentVariantOptionModel) { return html`
      - ${variant.segment ? variant.segment + ' - ' : ''}${variant.name} -
      ${this.#renderVariantStatus(variant)}
      - ${variant.isMandatory && variant.state !== UmbDocumentVariantState.PUBLISHED + ${option.variant?.segment ? option.variant.segment + ' - ' : ''}${option.variant?.name ?? + option.language.name} +
      ${this.#renderVariantStatus(option)}
      + ${option.language.isMandatory && option.variant?.state !== UmbDocumentVariantState.PUBLISHED ? html`
      Mandatory language
      ` @@ -126,16 +141,17 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement<
      `; } - #renderVariantStatus(variant: UmbDocumentVariantModel) { - switch (variant.state) { + #renderVariantStatus(option: UmbDocumentVariantOptionModel) { + switch (option.variant?.state) { case UmbDocumentVariantState.PUBLISHED: return this.localize.term('content_published'); case UmbDocumentVariantState.PUBLISHED_PENDING_CHANGES: return this.localize.term('content_publishedPendingChanges'); - case UmbDocumentVariantState.NOT_CREATED: case UmbDocumentVariantState.DRAFT: - default: return this.localize.term('content_unpublished'); + case UmbDocumentVariantState.NOT_CREATED: + default: + return this.localize.term('content_notCreated'); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.stories.ts index 19d38ffac6..0b20dbbaaf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.stories.ts @@ -12,17 +12,29 @@ import { html } from '@umbraco-cms/backoffice/external/lit'; const modalData: UmbDocumentVariantPickerModalData = { type: 'save', - variants: [ + options: [ { - name: 'English', - culture: 'en-us', - state: UmbDocumentVariantState.PUBLISHED, - createDate: '2021-08-25T14:00:00Z', - publishDate: null, - updateDate: null, - segment: null, - isMandatory: true, + unique: 'en-us', + variant: { + name: 'English variant name', + culture: 'en-us', + state: UmbDocumentVariantState.PUBLISHED, + createDate: '2021-08-25T14:00:00Z', + publishDate: null, + updateDate: null, + segment: null, + }, + language: { + entityType: 'language', + name: 'English', + unique: 'en-us', + isDefault: true, + isMandatory: true, + fallbackIsoCode: null, + }, }, + /* + // TODO: We do not support segments currently { name: 'English', culture: 'en-us', @@ -31,17 +43,27 @@ const modalData: UmbDocumentVariantPickerModalData = { publishDate: null, updateDate: null, segment: 'GTM', - isMandatory: true, }, + */ { - name: 'Danish', - culture: 'da-dk', - state: UmbDocumentVariantState.NOT_CREATED, - createDate: null, - publishDate: null, - updateDate: null, - segment: null, - isMandatory: false, + unique: 'da-dk', + variant: { + name: 'Danish variant name', + culture: 'da-dk', + state: UmbDocumentVariantState.NOT_CREATED, + createDate: null, + publishDate: null, + updateDate: null, + segment: null, + }, + language: { + entityType: 'language', + name: 'Danish', + unique: 'da-dk', + isDefault: false, + isMandatory: false, + fallbackIsoCode: null, + }, }, ], }; @@ -80,7 +102,6 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { publishDate: '2021-08-25T14:00:00Z', updateDate: null, segment: null, - isMandatory: true, }, { name: 'English', @@ -90,7 +111,6 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { publishDate: '2021-08-25T14:00:00Z', updateDate: null, segment: 'GTM', - isMandatory: false, }, { name: 'Danish', @@ -100,7 +120,6 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { publishDate: null, updateDate: null, segment: null, - isMandatory: false, }, ], } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.token.ts index 4c1d67688b..e5b2c17c90 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.token.ts @@ -1,14 +1,14 @@ import { UMB_DOCUMENT_VARIANT_PICKER_MODAL_ALIAS } from '../manifests.js'; -import type { UmbDocumentVariantModel } from '../../types.js'; +import type { UmbDocumentVariantOptionModel } from '../../types.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; export interface UmbDocumentVariantPickerModalData { type: 'save' | 'publish' | 'schedule' | 'unpublish'; - variants: Array; + options: Array; } export interface UmbDocumentVariantPickerModalValue { - selection: Array; + selection: Array; } export const UMB_DOCUMENT_LANGUAGE_PICKER_MODAL = new UmbModalToken< diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts index 85526b3f5f..b2e8e57394 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts @@ -46,7 +46,7 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts index 1ca0862574..7c070f022f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts @@ -1,5 +1,5 @@ import type { UmbDocumentEntityType } from './entity.js'; -import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant'; +import type { UmbVariantModel, UmbVariantOptionModel } from '@umbraco-cms/backoffice/variant'; import type { UmbReferenceById } from '@umbraco-cms/backoffice/models'; import { DocumentVariantStateModel as UmbDocumentVariantState } from '@umbraco-cms/backoffice/external/backend-api'; export { UmbDocumentVariantState }; @@ -22,7 +22,6 @@ export interface UmbDocumentDetailModel { export interface UmbDocumentVariantModel extends UmbVariantModel { state: UmbDocumentVariantState | null; publishDate: string | null; - isMandatory: boolean; } export interface UmbDocumentUrlInfoModel { @@ -36,3 +35,5 @@ export interface UmbDocumentValueModel { alias: string; value: ValueType; } + +export interface UmbDocumentVariantOptionModel extends UmbVariantOptionModel {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts index f623b6011c..2d7715adba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts @@ -1,64 +1,36 @@ +import type { UmbDocumentVariantOptionModel } from '../types.js'; import { UmbDocumentWorkspaceSplitViewElement } from './document-workspace-split-view.element.js'; import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from './document-workspace.context-token.js'; import { customElement, state, css, html } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace'; import type { UmbRoute, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; -import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant'; -import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; -import { combineLatest } from '@umbraco-cms/backoffice/external/rxjs'; +// TODO: This seem fully identical with Media Workspace Editor, so we can refactor this to a generic component. [NL] @customElement('umb-document-workspace-editor') export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { - //private _defaultVariant?: VariantViewModelBaseModel; - - // TODO: Refactor: when having a split view/variants context token, we can rename the split view/variants component to a generic and make this component generic as well. + // + // TODO: Refactor: when having a split view/variants context token, we can rename the split view/variants component to a generic and make this component generic as well. [NL] private splitViewElement = new UmbDocumentWorkspaceSplitViewElement(); + #workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; + @state() _routes?: Array; - @state() - _availableVariants: Array = []; - - @state() - _workspaceSplitViews: Array = []; - - #workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; - constructor() { super(); this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance; this.#observeVariants(); - this.#observeSplitViews(); }); } #observeVariants() { if (!this.#workspaceContext) return; - this.observe( - this.#workspaceContext.allowedVariants, - (variants) => { - this._availableVariants = variants; - this._generateRoutes(); - }, - '_observeVariants', - ); - } - - #observeSplitViews() { - if (!this.#workspaceContext) return; - this.observe( - this.#workspaceContext.splitView.activeVariantsInfo, - (variants) => { - this._workspaceSplitViews = variants; - }, - '_observeSplitViews', - ); + // TODO: the variantOptions observable is like too broad as this will be triggered then there is any change in the variant options, we need to only update routes when there is a relevant change to them. [NL] + this.observe(this.#workspaceContext.variantOptions, (options) => this._generateRoutes(options), '_observeVariants'); } private _handleVariantFolderPart(index: number, folderPart: string) { @@ -68,17 +40,18 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { this.#workspaceContext?.splitView.setActiveVariant(index, culture, segment); } - private async _generateRoutes() { - if (!this._availableVariants || this._availableVariants.length === 0) return; + private async _generateRoutes(options: Array) { + if (!options || options.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) => { + options.forEach((variantA) => { + options.forEach((variantB) => { routes.push({ - path: new UmbVariantId(variantA).toString() + '_&_' + new UmbVariantId(variantB).toString(), + // TODO: When implementing Segments, be aware if using the unique is URL Safe... [NL] + path: variantA.unique + '_&_' + variantB.unique, component: this.splitViewElement, setup: (_component, info) => { // Set split view/active info.. @@ -92,9 +65,10 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { }); // Single view: - this._availableVariants.forEach((variant) => { + options.forEach((variant) => { routes.push({ - path: new UmbVariantId(variant).toString(), + // TODO: When implementing Segments, be aware if using the unique is URL Safe... [NL] + path: variant.unique, component: this.splitViewElement, setup: (_component, info) => { // cause we might come from a split-view, we need to reset index 1. @@ -108,11 +82,21 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { // 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, + redirectTo: routes[options.length * options.length]?.path, }); } + const oldValue = this._routes; + + // is there any differences in the amount ot the paths? [NL] + // TODO: if we make a memorization function as the observer, we can avoid this check and avoid the whole build of routes. [NL] + if (oldValue && oldValue.length === routes.length) { + // is there any differences in the paths? [NL] + const hasDifferences = oldValue.some((route, index) => route.path !== routes[index].path); + if (!hasDifferences) return; + } this._routes = routes; + this.requestUpdate('_routes', oldValue); } private _gotWorkspaceRoute = (e: UmbRouterSlotInitEvent) => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 0c30839953..cc5e6fa680 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -2,12 +2,15 @@ import { UmbDocumentTypeDetailRepository } from '../../document-types/repository import { UmbDocumentPropertyDataContext } from '../property-dataset-context/document-property-dataset-context.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; import { UmbDocumentDetailRepository } from '../repository/index.js'; -import { UmbDocumentVariantState, type UmbDocumentDetailModel, type UmbDocumentVariantModel } from '../types.js'; -import type { UmbDocumentVariantPickerModalData } from '../modals/index.js'; +import type { UmbDocumentDetailModel, UmbDocumentVariantModel, UmbDocumentVariantOptionModel } from '../types.js'; +import { UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, type UmbDocumentVariantPickerModalData } from '../modals/index.js'; import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js'; -import { UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT } from '../global-contexts/document-variant-manager.context.js'; import { UMB_DOCUMENT_WORKSPACE_ALIAS } from './manifests.js'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { + type UmbObjectWithVariantProperties, + UmbVariantId, + variantPropertiesObjectToString, +} from '@umbraco-cms/backoffice/variant'; import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; import { UmbEditableWorkspaceContextBase, @@ -25,11 +28,12 @@ import { import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; type EntityType = UmbDocumentDetailModel; export class UmbDocumentWorkspaceContext extends UmbEditableWorkspaceContextBase - implements UmbVariantableWorkspaceContextInterface, UmbPublishableWorkspaceContextInterface + implements UmbVariantableWorkspaceContextInterface, UmbPublishableWorkspaceContextInterface { // public readonly repository = new UmbDocumentDetailRepository(this); @@ -40,7 +44,7 @@ export class UmbDocumentWorkspaceContext */ #currentData = new UmbObjectState(undefined); #getDataPromise?: Promise; - #variantManagerContext?: typeof UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT.TYPE; + // TODo: Optimize this so it uses either a App Language Context? [NL] #languageRepository = new UmbLanguageCollectionRepository(this); #languages = new UmbArrayState([], (x) => x.unique); public readonly languages = this.#languages.asObservable(); @@ -54,26 +58,18 @@ export class UmbDocumentWorkspaceContext readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.documentType.unique); readonly contentTypeHasCollection = this.#currentData.asObservablePart((data) => !!data?.documentType.collection); readonly variants = this.#currentData.asObservablePart((data) => data?.variants ?? []); - readonly allowedVariants = combineObservables([this.variants, this.languages], ([variants, languages]) => { - const missingLanguages = languages.filter((x) => !variants.some((v) => v.culture === x.unique)); - const newVariants = variants.concat( - missingLanguages.map( - (language) => - ({ - state: UmbDocumentVariantState.NOT_CREATED, - isMandatory: language.isMandatory, - culture: language.unique, - segment: null, - name: language.name, - createDate: null, - publishDate: null, - updateDate: null, - }) as UmbDocumentVariantModel, - ), - ); - return newVariants; + readonly variantOptions = combineObservables([this.variants, this.languages], ([variants, languages]) => { + return languages.map((language) => { + return { + variant: variants.find((x) => x.culture === language.unique), + language, + // TODO: When including segments, this should be updated to include the segment as well. [NL] + unique: language.unique, // This must be a variantId string! + } as UmbDocumentVariantOptionModel; + }); }); - readonly changedVariants = new UmbArrayState([], (x) => x.compare); + + readonly changedVariants = new UmbArrayState([], variantPropertiesObjectToString); readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []); readonly templateId = this.#currentData.asObservablePart((data) => data?.template?.unique || null); @@ -83,16 +79,13 @@ export class UmbDocumentWorkspaceContext constructor(host: UmbControllerHost) { super(host, UMB_DOCUMENT_WORKSPACE_ALIAS); - this.consumeContext(UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT, (instance) => { - this.#variantManagerContext = instance; - }); - this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique)); this.loadLanguages(); } async loadLanguages() { + // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] const { data } = await this.#languageRepository.requestCollection({}); this.#languages.setValue(data?.items ?? []); } @@ -222,50 +215,13 @@ export class UmbDocumentWorkspaceContext const data = this.getData(); if (!data) throw new Error('Data is missing'); if (!data.unique) throw new Error('Unique is missing'); - if (!this.#variantManagerContext) throw new Error('Variant manager context is missing'); const activeVariants = this.splitView.getActiveVariants(); - const activeVariant = activeVariants.length ? activeVariants[0] : undefined; - // Calculate variants - const pickedVariants: string[] = []; - const availableVariants = await firstValueFrom(this.allowedVariants); - let allowedVariants = data.variants; + const pickedVariants = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant).toString()); + const allowedVariants = await firstValueFrom(this.variantOptions); - // Make sure that the active variant is in the allowed variants - if (activeVariant) { - const activeVariantInAvailableVariants = availableVariants.find( - (x) => x.culture === activeVariant.culture && x.segment === activeVariant.segment, - ); - if (activeVariantInAvailableVariants) { - pickedVariants.push(activeVariantInAvailableVariants.culture!); - allowedVariants = appendToFrozenArray( - allowedVariants, - activeVariantInAvailableVariants, - (x) => - x.culture === activeVariantInAvailableVariants.culture && - x.segment === activeVariantInAvailableVariants.segment, - ); - } - } - - // Make sure the changed variants are in the allowed variants - const changedVariants = this.changedVariants.getValue(); - if (changedVariants.length) { - pickedVariants.push(...changedVariants.map((x) => x.culture!)); - const changedVariantsInAvailableVariants = availableVariants.filter((x) => - changedVariants.some((y) => y.equal(new UmbVariantId(x))), - ); - for (const changedVariant of changedVariantsInAvailableVariants) { - allowedVariants = appendToFrozenArray( - allowedVariants, - changedVariant, - (x) => changedVariant.culture === x.culture && changedVariant.segment === x.segment, - ); - } - } - - const selectedVariants = await this.#variantManagerContext.pickVariants(allowedVariants, type, pickedVariants); + const selectedVariants = await this.pickVariants(type, allowedVariants, pickedVariants); // If no variants are selected, we don't save anything. if (!selectedVariants.length) return []; @@ -281,6 +237,35 @@ export class UmbDocumentWorkspaceContext return selectedVariants; } + // TODO: refactor this part so it can be utilized by others? [NL] + async pickVariants( + type: UmbDocumentVariantPickerModalData['type'], + availableVariants: Array, + selectedVariants?: Array, + ): Promise { + // If there is only one variant, we don't need to select anything. + if (availableVariants.length === 1) { + // TODO: we are missing a good way to make a variantId from a variantOptionModel. [NL] + return [new UmbVariantId(availableVariants[0].language.unique, null)]; + } + + const modalManagerContext = await this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, () => {}).asPromise(); + + const modalData: UmbDocumentVariantPickerModalData = { + type, + options: availableVariants, + }; + + const modalContext = modalManagerContext.open(UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, { + data: modalData, + value: { selection: selectedVariants?.map((x) => x.toString()) ?? [] }, + }); + + const result = await modalContext.onSubmit().catch(() => undefined); + + return result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; + } + async save() { await this.#createOrSave('save'); const data = this.getData(); @@ -304,9 +289,11 @@ export class UmbDocumentWorkspaceContext const unique = this.getEntityId(); if (!unique) throw new Error('Unique is missing'); - if (!this.#variantManagerContext) throw new Error('Variant manager context is missing'); + //if (!this.#variantManagerContext) throw new Error('Variant manager context is missing'); - this.#variantManagerContext.unpublish(unique); + //this.#variantManagerContext.unpublish(unique); + alert('not implemented'); + throw new Error('Not implemented'); } async delete() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/types.ts index 92427057b9..ad8454fffb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/types.ts @@ -1,5 +1,5 @@ import type { UmbMediaEntityType } from './entity.js'; -import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant'; +import type { UmbVariantModel, UmbVariantOptionModel } from '@umbraco-cms/backoffice/variant'; import type { MediaUrlInfoModel, MediaValueModel } from '@umbraco-cms/backoffice/external/backend-api'; export interface UmbMediaDetailModel { @@ -12,3 +12,5 @@ export interface UmbMediaDetailModel { values: Array; variants: Array; } + +export interface UmbMediaVariantOptionModel extends UmbVariantOptionModel {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts index e5cc052897..f7d8f0e56a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace-editor.element.ts @@ -1,28 +1,19 @@ +import type { UmbMediaVariantOptionModel } from '../types.js'; import { UmbMediaWorkspaceSplitViewElement } from './media-workspace-split-view.element.js'; import { UMB_MEDIA_WORKSPACE_CONTEXT } from './media-workspace.context-token.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { customElement, state, css, html } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { UmbRoute, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; -import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace'; @customElement('umb-media-workspace-editor') export class UmbMediaWorkspaceEditorElement extends UmbLitElement { - //private _defaultVariant?: VariantViewModelBaseModel; - - // TODO: Refactor: when having a split view/variants context token, we can rename the split view/variants component to a generic and make this component generic as well. + // + // TODO: Refactor: when having a split view/variants context token, we can rename the split view/variants component to a generic and make this component generic as well. [NL] private splitViewElement = new UmbMediaWorkspaceSplitViewElement(); @state() _routes?: Array; - @state() - _availableVariants: Array = []; - - @state() - _workspaceSplitViews: Array = []; - #workspaceContext?: typeof UMB_MEDIA_WORKSPACE_CONTEXT.TYPE; constructor() { @@ -31,31 +22,12 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement { this.consumeContext(UMB_MEDIA_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance; this.#observeVariants(); - this.#observeSplitViews(); }); } #observeVariants() { if (!this.#workspaceContext) return; - this.observe( - this.#workspaceContext.allowedVariants, - (variants) => { - this._availableVariants = variants; - this._generateRoutes(); - }, - '_observeVariants', - ); - } - - #observeSplitViews() { - if (!this.#workspaceContext) return; - this.observe( - this.#workspaceContext.splitView.activeVariantsInfo, - (variants) => { - this._workspaceSplitViews = variants; - }, - '_observeSplitViews', - ); + this.observe(this.#workspaceContext.variantOptions, (options) => this._generateRoutes(options), '_observeVariants'); } private _handleVariantFolderPart(index: number, folderPart: string) { @@ -65,17 +37,18 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement { this.#workspaceContext?.splitView.setActiveVariant(index, culture, segment); } - private _generateRoutes() { - if (!this._availableVariants || this._availableVariants.length === 0) return; + private async _generateRoutes(variants: Array) { + if (!variants || variants.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) => { + variants.forEach((variantA) => { + variants.forEach((variantB) => { routes.push({ - path: UmbVariantId.Create(variantA).toString() + '_&_' + UmbVariantId.Create(variantB).toString(), + // TODO: When implementing Segments, be aware if using the unique is URL Safe... [NL] + path: variantA.unique + '_&_' + variantB.unique, component: this.splitViewElement, setup: (_component, info) => { // Set split view/active info.. @@ -89,9 +62,10 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement { }); // Single view: - this._availableVariants.forEach((variant) => { + variants.forEach((variant) => { routes.push({ - path: UmbVariantId.Create(variant).toString(), + // TODO: When implementing Segments, be aware if using the unique is URL Safe... [NL] + path: variant.unique, component: this.splitViewElement, setup: (_component, info) => { // cause we might come from a split-view, we need to reset index 1. @@ -105,11 +79,21 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement { // 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, + redirectTo: routes[variants.length * variants.length]?.path, }); } + const oldValue = this._routes; + + // is there any differences in the amount ot the paths? [NL] + // TODO: if we make a memorization function as the observer, we can avoid this check and avoid the whole build of routes. [NL] + if (oldValue && oldValue.length === routes.length) { + // is there any differences in the paths? [NL] + const hasDifferences = oldValue.some((route, index) => route.path !== routes[index].path); + if (!hasDifferences) return; + } this._routes = routes; + this.requestUpdate('_routes', oldValue); } private _gotWorkspaceRoute = (e: UmbRouterSlotInitEvent) => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts index f65aa898f2..fa42e589d0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts @@ -2,7 +2,7 @@ import { UmbMediaTypeDetailRepository } from '../../media-types/repository/detai import { UmbMediaPropertyDataContext } from '../property-dataset-context/media-property-dataset-context.js'; import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js'; import { UmbMediaDetailRepository } from '../repository/index.js'; -import type { UmbMediaDetailModel } from '../types.js'; +import type { UmbMediaDetailModel, UmbMediaVariantOptionModel } from '../types.js'; import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; import { @@ -33,8 +33,11 @@ export class UmbMediaWorkspaceContext */ #currentData = new UmbObjectState(undefined); #getDataPromise?: Promise; + // TODo: Optimize this so it uses either a App Language Context? [NL] #languageRepository = new UmbLanguageCollectionRepository(this); - #languageCollection = new UmbArrayState([], (x) => x.unique); + #languages = new UmbArrayState([], (x) => x.unique); + public readonly languages = this.#languages.asObservable(); + public isLoaded() { return this.#getDataPromise; } @@ -43,22 +46,16 @@ export class UmbMediaWorkspaceContext readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.mediaType.unique); readonly variants = this.#currentData.asObservablePart((data) => data?.variants || []); - readonly allowedVariants = combineObservables( - [this.variants, this.#languageCollection.asObservable()], - ([variants, languages]) => { - const missingLanguages = languages.filter((x) => !variants.some((v) => v.culture === x.unique)); - const newVariants = variants.concat( - missingLanguages.map((x) => ({ - culture: x.unique, - segment: null, - name: x.name, - createDate: '', - updateDate: '', - })), - ); - return newVariants; - }, - ); + readonly variantOptions = combineObservables([this.variants, this.languages], ([variants, languages]) => { + return languages.map((language) => { + return { + variant: variants.find((x) => x.culture === language.unique), + language, + // TODO: When including segments, this should be updated to include the segment as well. [NL] + unique: language.unique, // This must be a variantId string! + } as UmbMediaVariantOptionModel; + }); + }); readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []); readonly structure = new UmbContentTypePropertyStructureManager(this, new UmbMediaTypeDetailRepository(this)); @@ -73,7 +70,7 @@ export class UmbMediaWorkspaceContext async loadLanguages() { const { data } = await this.#languageRepository.requestCollection({}); - this.#languageCollection.setValue(data?.items ?? []); + this.#languages.setValue(data?.items ?? []); } async load(unique: string) { From a905ec30a969e2dcef8e62fdc685c9b179dc991b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 09:44:27 +0100 Subject: [PATCH 63/98] remove schedule --- .../src/packages/core/variant/variant-id.class.ts | 1 - .../global-contexts/document-variant-manager.context.temp_ts | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts index 2e6144b1c3..c131029e8a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts @@ -32,7 +32,6 @@ export class UmbVariantId { public readonly culture: string | null = null; public readonly segment: string | null = null; - public readonly schedule: { publishTime?: string | null; unpublishTime?: string | null } | null = null; constructor(culture?: string | null, segment?: string | null) { this.culture = (culture === UMB_INVARIANT_CULTURE ? null : culture?.toLowerCase()) ?? null; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts index d5323f8a55..09802ec8c6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts @@ -17,8 +17,6 @@ export class UmbDocumentVariantManagerContext extends UmbContextBase implements UmbApi { - #publishingRepository = new UmbDocumentPublishingRepository(this); - #documentRepository = new UmbDocumentDetailRepository(this); #modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; #appLanguageCulture?: string; @@ -88,6 +86,7 @@ export class UmbDocumentVariantManagerContext * @param documentUnique The unique identifier of the document. */ async publish(documentUnique: string) { + #documentRepository = new UmbDocumentDetailRepository(this); const { data } = await this.#documentRepository.requestByUnique(documentUnique); if (!data) throw new Error('Document not found'); const variantIds = await this.pickVariants( @@ -96,6 +95,7 @@ export class UmbDocumentVariantManagerContext this.#appLanguageCulture ? [this.#appLanguageCulture] : undefined, ); if (variantIds.length) { + #publishingRepository = new UmbDocumentPublishingRepository(this); await this.#publishingRepository.publish(documentUnique, variantIds); } } @@ -105,6 +105,7 @@ export class UmbDocumentVariantManagerContext * @param documentUnique The unique identifier of the document. */ async unpublish(documentUnique: string) { + #documentRepository = new UmbDocumentDetailRepository(this); const { data } = await this.#documentRepository.requestByUnique(documentUnique); if (!data) throw new Error('Document not found'); From 200442a1cec22795ce3276f4a90a808de279e39b Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:17:30 +0100 Subject: [PATCH 64/98] search function --- .../block-catalogue-modal.element.ts | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index 46179f300a..11832f9772 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -72,12 +72,12 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< } #onFilter() { - if (this._search.length < 3) { + if (this._search.length <= 3) { this._filtered = this._groupedBlocks; } else { const search = this._search.toLowerCase(); - this._filtered = this._groupedBlocks.filter((group) => { - return group.blocks.find((block) => block.label?.toLocaleLowerCase().includes(search)) ? true : false; + this._filtered = this._groupedBlocks.map((group) => { + return { ...group, blocks: group.blocks.filter((block) => block.label?.toLocaleLowerCase().includes(search)) }; }); } } @@ -109,37 +109,35 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< #renderCreateEmpty() { return html` - ${this._groupedBlocks.length > 7 + ${this.data?.blocks && this.data.blocks.length >= 10 ? html` ` : nothing} - ${this._groupedBlocks - ? this._groupedBlocks.map( - (group) => html` - ${group.name && group.name !== '' ? html`

      ${group.name}

      ` : nothing} -
      - ${repeat( - group.blocks, - (block) => block.contentElementTypeKey, - (block) => html` - - - `, - )} -
      - `, - ) - : ''} + ${this._filtered.map( + (group) => html` + ${group.name && group.name !== '' ? html`

      ${group.name}

      ` : nothing} +
      + ${repeat( + group.blocks, + (block) => block.contentElementTypeKey, + (block) => html` + + + `, + )} +
      + `, + )} `; } @@ -169,6 +167,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< #search { width: 100%; align-items: center; + margin-bottom: var(--uui-size-layout-1); } #search uui-icon { padding-left: var(--uui-size-space-3); From 4e132a85414f77db95f106a53a68e171393e9372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 10:32:04 +0100 Subject: [PATCH 65/98] modal method --- .../documents/documents/modals/index.ts | 1 + .../pick-document-variant-modal.controller.ts | 58 ++++++++++++++++ .../publish-document-modal.function._ts | 0 .../document-variant-picker-modal.token.ts | 4 +- .../workspace/document-workspace.context.ts | 66 +++++++------------ .../global-contexts/app-language.context.ts | 4 ++ 6 files changed, 91 insertions(+), 42 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-document-modal.function._ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/index.ts index 6db24e7b9a..1d4ae479e6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/index.ts @@ -1 +1,2 @@ export * from './variant-picker/index.js'; +export * from './pick-document-variant-modal.controller.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts new file mode 100644 index 0000000000..41fddc5896 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts @@ -0,0 +1,58 @@ +import type { UmbDocumentVariantOptionModel } from '../types.js'; +import { + UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, + type UmbDocumentVariantPickerModalData, + type UmbDocumentVariantPickerModalType, +} from './variant-picker/document-variant-picker-modal.token.js'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export interface UmbPickDocumentVariantModalArgs { + type: UmbDocumentVariantPickerModalType; + options: Array; + selected?: Array; +} + +export class UmbPickDocumentVariantModalController extends UmbBaseController { + async open(args: UmbPickDocumentVariantModalArgs): Promise { + const modalManagerContext = await this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, () => {}).asPromise(); + const selected = args.selected ?? []; + + const modalData: UmbDocumentVariantPickerModalData = { + type: args.type, + options: args.options, + }; + + if (selected.length === 0) { + // TODO: Make it possible to use consume context without callback. [NL] + const ctrl = this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (appLanguageContext) => {}); + const context = await ctrl.asPromise(); + const appCulture = context.getAppCulture(); + // If the app language is one of the options, select it by default: + if (appCulture && modalData.options.some((o) => o.language.unique === appCulture)) { + selected.push(new UmbVariantId(appCulture, null)); + } + ctrl.destroy(); + } + + const modalContext = modalManagerContext.open(UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, { + data: modalData, + // We need to turn the selected variant ids into strings for them to be serializable to the value state, in other words the value of a modal cannot hold class instances: + value: { selection: selected.map((x) => x.toString()) ?? [] }, + }); + + const result = await modalContext.onSubmit().catch(() => undefined); + + this.destroy(); + + // Map back into UmbVariantId instances: + return result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; + } +} + +export function umbPickDocumentVariantModal(host: UmbControllerHost, args: UmbPickDocumentVariantModalArgs) { + return new UmbPickDocumentVariantModalController(host).open(args); +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-document-modal.function._ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-document-modal.function._ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.token.ts index e5b2c17c90..8ce9615f0e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.token.ts @@ -2,8 +2,10 @@ import { UMB_DOCUMENT_VARIANT_PICKER_MODAL_ALIAS } from '../manifests.js'; import type { UmbDocumentVariantOptionModel } from '../../types.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +export type UmbDocumentVariantPickerModalType = 'save' | 'publish' | 'schedule' | 'unpublish'; + export interface UmbDocumentVariantPickerModalData { - type: 'save' | 'publish' | 'schedule' | 'unpublish'; + type: UmbDocumentVariantPickerModalType; options: Array; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index cc5e6fa680..ad91e108e8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -3,7 +3,7 @@ import { UmbDocumentPropertyDataContext } from '../property-dataset-context/docu import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; import { UmbDocumentDetailRepository } from '../repository/index.js'; import type { UmbDocumentDetailModel, UmbDocumentVariantModel, UmbDocumentVariantOptionModel } from '../types.js'; -import { UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, type UmbDocumentVariantPickerModalData } from '../modals/index.js'; +import { umbPickDocumentVariantModal, type UmbDocumentVariantPickerModalType } from '../modals/index.js'; import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js'; import { UMB_DOCUMENT_WORKSPACE_ALIAS } from './manifests.js'; import { @@ -28,7 +28,6 @@ import { import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; -import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; type EntityType = UmbDocumentDetailModel; export class UmbDocumentWorkspaceContext @@ -211,21 +210,35 @@ export class UmbDocumentWorkspaceContext } } - async #createOrSave(type: UmbDocumentVariantPickerModalData['type']): Promise { - const data = this.getData(); - if (!data) throw new Error('Data is missing'); - if (!data.unique) throw new Error('Unique is missing'); - + async #pickVariantsForAction(type: UmbDocumentVariantPickerModalType): Promise { const activeVariants = this.splitView.getActiveVariants(); - const pickedVariants = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant).toString()); - const allowedVariants = await firstValueFrom(this.variantOptions); + // TODO: Picked variants should include the ones that has been changed (but not jet saved) this requires some more awareness about the state of runtime data. [NL] + const selected = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant)); + const options = await firstValueFrom(this.variantOptions); - const selectedVariants = await this.pickVariants(type, allowedVariants, pickedVariants); + // If there is only one variant, we don't need to open the modal. + if (options.length === 0) { + throw new Error('No variants are available'); + } else if (options.length === 1) { + // If only one option we will skip ahead and save the document with the only variant available: + const firstVariant = new UmbVariantId(options[0].language.unique, null); + return await this.#performSaveOrCreate([firstVariant]); + } + + const selectedVariants = await umbPickDocumentVariantModal(this, { type, options, selected }); // If no variants are selected, we don't save anything. if (!selectedVariants.length) return []; + return await this.#performSaveOrCreate(selectedVariants); + } + + async #performSaveOrCreate(selectedVariants: Array) { + const data = this.getData(); + if (!data) throw new Error('Data is missing'); + if (!data.unique) throw new Error('Unique is missing'); + if (this.getIsNew()) { if ((await this.repository.create(data)).data !== undefined) { this.setIsNew(false); @@ -237,44 +250,15 @@ export class UmbDocumentWorkspaceContext return selectedVariants; } - // TODO: refactor this part so it can be utilized by others? [NL] - async pickVariants( - type: UmbDocumentVariantPickerModalData['type'], - availableVariants: Array, - selectedVariants?: Array, - ): Promise { - // If there is only one variant, we don't need to select anything. - if (availableVariants.length === 1) { - // TODO: we are missing a good way to make a variantId from a variantOptionModel. [NL] - return [new UmbVariantId(availableVariants[0].language.unique, null)]; - } - - const modalManagerContext = await this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, () => {}).asPromise(); - - const modalData: UmbDocumentVariantPickerModalData = { - type, - options: availableVariants, - }; - - const modalContext = modalManagerContext.open(UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, { - data: modalData, - value: { selection: selectedVariants?.map((x) => x.toString()) ?? [] }, - }); - - const result = await modalContext.onSubmit().catch(() => undefined); - - return result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; - } - async save() { - await this.#createOrSave('save'); + await this.#pickVariantsForAction('save'); const data = this.getData(); if (!data) throw new Error('Data is missing'); this.saveComplete(data); } public async publish() { - const variantIds = await this.#createOrSave('publish'); + const variantIds = await this.#pickVariantsForAction('publish'); const unique = this.getEntityId(); if (variantIds.length && unique) { await this.publishingRepository.publish(unique, variantIds); diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/global-contexts/app-language.context.ts b/src/Umbraco.Web.UI.Client/src/packages/language/global-contexts/app-language.context.ts index eb077dcfe5..e26d796c27 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/global-contexts/app-language.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/global-contexts/app-language.context.ts @@ -13,6 +13,10 @@ export class UmbAppLanguageContext extends UmbBaseController implements UmbApi { appLanguage = this.#appLanguage.asObservable(); appLanguageCulture = this.#appLanguage.asObservablePart((x) => x?.unique); + getAppCulture() { + return this.#appLanguage.getValue()?.unique; + } + constructor(host: UmbControllerHost) { super(host); this.provideContext(UMB_APP_LANGUAGE_CONTEXT, this); From 23c3de318d4c6daa9aaaa1816beea9aa1a2e7192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 10:47:29 +0100 Subject: [PATCH 66/98] entity actions --- .../entity-actions/publish.action.ts | 35 ++++- .../entity-actions/unpublish.action.ts | 35 ++++- .../document-variant-manager.context.temp_ts | 131 ------------------ .../pick-document-variant-modal.controller.ts | 1 + 4 files changed, 63 insertions(+), 139 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts index 0276e0f366..4c5dd7a0ac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts @@ -1,10 +1,37 @@ -import type { UmbDocumentPublishingRepository } from '../repository/index.js'; +import { umbPickDocumentVariantModal } from '../modals/pick-document-variant-modal.controller.js'; +import { UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '../repository/index.js'; +import { UmbDocumentVariantState } from '../types.js'; +import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; export class UmbPublishDocumentEntityAction extends UmbEntityActionBase { async execute() { - throw new Error('This action not implemented.'); - //if (!this.#variantManagerContext) throw new Error('Variant manager context is missing'); - //await this.#variantManagerContext.publish(this.unique); + const languageRepository = new UmbLanguageCollectionRepository(this._host); + const { data: languageData } = await languageRepository.requestCollection({}); + + const documentRepository = new UmbDocumentDetailRepository(this._host); + const { data: documentData } = await documentRepository.requestByUnique(this.unique); + + const allOptions = (languageData?.items ?? []).map((language) => ({ + language: language, + variant: documentData?.variants.find((variant) => variant.culture === language.unique), + unique: new UmbVariantId(language.unique, null).toString(), + })); + + // Only display variants that are relevant to pick from, i.e. variants that are draft or published with pending changes: + const options = allOptions.filter( + (option) => + option.variant && + (option.variant.state === UmbDocumentVariantState.DRAFT || + option.variant.state === UmbDocumentVariantState.PUBLISHED_PENDING_CHANGES), + ); + + const selectedVariants = await umbPickDocumentVariantModal(this, { type: 'publish', options }); + + if (selectedVariants.length) { + const publishingRepository = new UmbDocumentPublishingRepository(this._host); + await publishingRepository.publish(this.unique, selectedVariants); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts index cf34e477e1..dc333eb654 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts @@ -1,10 +1,37 @@ -import type { UmbDocumentPublishingRepository } from '../repository/index.js'; +import { umbPickDocumentVariantModal } from '../modals/pick-document-variant-modal.controller.js'; +import { UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '../repository/index.js'; +import { UmbDocumentVariantState } from '../types.js'; +import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase { async execute() { - throw new Error('This action not implemented.'); - //if (!this.#variantManagerContext) throw new Error('Variant manager context is missing'); - //await this.#variantManagerContext.unpublish(this.unique); + const languageRepository = new UmbLanguageCollectionRepository(this._host); + const { data: languageData } = await languageRepository.requestCollection({}); + + const documentRepository = new UmbDocumentDetailRepository(this._host); + const { data: documentData } = await documentRepository.requestByUnique(this.unique); + + const allOptions = (languageData?.items ?? []).map((language) => ({ + language: language, + variant: documentData?.variants.find((variant) => variant.culture === language.unique), + unique: new UmbVariantId(language.unique, null).toString(), + })); + + // Only display variants that are relevant to pick from, i.e. variants that are published or published with pending changes: + const options = allOptions.filter( + (option) => + option.variant && + (option.variant.state === UmbDocumentVariantState.PUBLISHED || + option.variant.state === UmbDocumentVariantState.PUBLISHED_PENDING_CHANGES), + ); + + const selectedVariants = await umbPickDocumentVariantModal(this, { type: 'publish', options }); + + if (selectedVariants.length) { + const publishingRepository = new UmbDocumentPublishingRepository(this._host); + await publishingRepository.unpublish(this.unique, selectedVariants); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts deleted file mode 100644 index 09802ec8c6..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-variant-manager.context.temp_ts +++ /dev/null @@ -1,131 +0,0 @@ -import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../types.js'; -import { UmbDocumentDetailRepository } from '../repository/detail/document-detail.repository.js'; -import { - UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, - type UmbDocumentVariantPickerModalData, -} from '../modals/variant-picker/index.js'; -import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js'; -import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; -import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; -import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language'; - -export class UmbDocumentVariantManagerContext - extends UmbContextBase - implements UmbApi -{ - #modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; - #appLanguageCulture?: string; - - constructor(host: UmbControllerHost) { - super(host, UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalManagerContext = instance; - }); - - this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (appLanguageContext) => { - this.observe(appLanguageContext.appLanguageCulture, (culture) => { - this.#appLanguageCulture = culture?.toLowerCase(); - }); - }); - } - - /** - * Helps the user pick variants for a specific operation. - * If there is only one variant, it will be selected automatically. - * If there are multiple variants, a modal will be shown to the user. - * @param type The type of operation to perform. - * @param documentUnique The unique identifier of the document. - * @param activeVariantCulture The culture of the active variant (will be pre-selected in the modal). - * @param filterFn Optional filter function to filter the available variants. - * @returns The selected variants to perform the operation on. - */ - async pickVariants( - type: UmbDocumentVariantPickerModalData['type'], - availableVariants: Array, - activeVariantCultures?: Array, - ): Promise { - // If there is only one variant, we don't need to select anything. - if (availableVariants.length === 1) { - return [UmbVariantId.Create(availableVariants[0])]; - } - - if (!this.#modalManagerContext) throw new Error('Modal manager context is missing'); - - const modalData: UmbDocumentVariantPickerModalData = { - type, - options: availableVariants, - }; - - const modalContext = this.#modalManagerContext.open(UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, { - data: modalData, - value: { selection: activeVariantCultures ?? [] }, - }); - - const result = await modalContext.onSubmit().catch(() => undefined); - - if (!result?.selection.length) return []; - - const selectedVariants = result.selection.map((x) => x?.toLowerCase() ?? ''); - - // Match the result to the available variants. - // Why would this be needed if the modal is only showing available variants? [NL] - const variantIds = availableVariants - .filter((x) => selectedVariants.includes(x.culture!)) - .map((x) => UmbVariantId.Create(x)); - - return variantIds; - } - - /** - * Publish the latest version of a document indescriminately. - * @param documentUnique The unique identifier of the document. - */ - async publish(documentUnique: string) { - #documentRepository = new UmbDocumentDetailRepository(this); - const { data } = await this.#documentRepository.requestByUnique(documentUnique); - if (!data) throw new Error('Document not found'); - const variantIds = await this.pickVariants( - 'publish', - data.variants, - this.#appLanguageCulture ? [this.#appLanguageCulture] : undefined, - ); - if (variantIds.length) { - #publishingRepository = new UmbDocumentPublishingRepository(this); - await this.#publishingRepository.publish(documentUnique, variantIds); - } - } - - /** - * Unpublish the latest version of a document indescriminately. - * @param documentUnique The unique identifier of the document. - */ - async unpublish(documentUnique: string) { - #documentRepository = new UmbDocumentDetailRepository(this); - const { data } = await this.#documentRepository.requestByUnique(documentUnique); - if (!data) throw new Error('Document not found'); - - // Only show published variants - const variants = data.variants.filter((variant) => variant.state === UmbDocumentVariantState.PUBLISHED); - - const variantIds = await this.pickVariants( - 'unpublish', - variants, - this.#appLanguageCulture ? [this.#appLanguageCulture] : undefined, - ); - - if (variantIds.length) { - await this.#publishingRepository.unpublish(documentUnique, variantIds); - } - } -} - -export default UmbDocumentVariantManagerContext; - -export const UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT = new UmbContextToken( - 'UmbDocumentVariantManagerContext', -); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts index 41fddc5896..2e6a28b7fd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts @@ -46,6 +46,7 @@ export class UmbPickDocumentVariantModalController extends UmbBaseController { const result = await modalContext.onSubmit().catch(() => undefined); + // This is a one time off, so we can destroy our selfs. this.destroy(); // Map back into UmbVariantId instances: From dba3f27a35ea6b1971de9ea646dbf60abd36be21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 10:57:31 +0100 Subject: [PATCH 67/98] comment on repository size --- .../documents/documents/entity-actions/publish.action.ts | 1 + .../documents/documents/entity-actions/unpublish.action.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts index 4c5dd7a0ac..cf8dbb3251 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts @@ -10,6 +10,7 @@ export class UmbPublishDocumentEntityAction extends UmbEntityActionBase Date: Tue, 27 Feb 2024 11:03:11 +0100 Subject: [PATCH 68/98] correct to unpublish --- .../documents/documents/entity-actions/unpublish.action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts index db450cc88f..5f5cdf5a01 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts @@ -28,7 +28,7 @@ export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase Date: Tue, 27 Feb 2024 11:04:42 +0100 Subject: [PATCH 69/98] note on no options scenario --- .../modals/pick-document-variant-modal.controller.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts index 2e6a28b7fd..9aedbb9009 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts @@ -26,6 +26,10 @@ export class UmbPickDocumentVariantModalController extends UmbBaseController { options: args.options, }; + if (modalData.options.length === 0) { + // TODO: What do to when there is no options? + } + if (selected.length === 0) { // TODO: Make it possible to use consume context without callback. [NL] const ctrl = this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (appLanguageContext) => {}); From e5692e14b254d02576737d583a476db04f6508f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 11:06:50 +0100 Subject: [PATCH 70/98] note on missing feature --- .../documents/documents/entity-actions/publish.action.ts | 1 + .../documents/documents/entity-actions/unpublish.action.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts index cf8dbb3251..aa90aec2e2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts @@ -28,6 +28,7 @@ export class UmbPublishDocumentEntityAction extends UmbEntityActionBase Date: Tue, 27 Feb 2024 11:55:32 +0100 Subject: [PATCH 71/98] Unpublish --- .../packages/core/action/repository-action.ts | 4 ++-- .../core/entity-action/entity-action.ts | 6 +++--- .../workspace/document-workspace.context.ts | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/action/repository-action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/action/repository-action.ts index 37875ffaef..ebcb378547 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/action/repository-action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/action/repository-action.ts @@ -1,4 +1,4 @@ -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; import { type UmbApi, UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @@ -6,7 +6,7 @@ import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registr export class UmbActionBase extends UmbBaseController implements UmbApi { repository?: RepositoryType; - constructor(host: UmbControllerHostElement, repositoryAlias: string) { + constructor(host: UmbControllerHost, repositoryAlias: string) { super(host); new UmbExtensionApiInitializer(this, umbExtensionsRegistry, repositoryAlias, [this._host], (permitted, ctrl) => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/entity-action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/entity-action.ts index 8b2e890e32..2aea30ffbd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/entity-action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/entity-action.ts @@ -1,6 +1,6 @@ -import type { UmbAction} from '../action/index.js'; +import type { UmbAction } from '../action/index.js'; import { UmbActionBase } from '../action/index.js'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export interface UmbEntityAction extends UmbAction { unique: string; @@ -11,7 +11,7 @@ export class UmbEntityActionBase extends UmbActionBase(undefined); #currentData = new UmbObjectState(undefined); #getDataPromise?: Promise; // TODo: Optimize this so it uses either a App Language Context? [NL] @@ -68,7 +70,6 @@ export class UmbDocumentWorkspaceContext }); }); - readonly changedVariants = new UmbArrayState([], variantPropertiesObjectToString); readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []); readonly templateId = this.#currentData.asObservablePart((data) => data?.template?.unique || null); @@ -95,7 +96,7 @@ export class UmbDocumentWorkspaceContext if (!data) return undefined; this.setIsNew(false); - //this.#persisted.next(data); + this.#persistedData.setValue(data); this.#currentData.setValue(data); return data || undefined; } @@ -206,7 +207,8 @@ export class UmbDocumentWorkspaceContext (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), ); this.#currentData.update({ values }); - this.changedVariants.appendOne(variantId); + + // TODO: Ensure variant object.. } } @@ -254,6 +256,10 @@ export class UmbDocumentWorkspaceContext await this.#pickVariantsForAction('save'); const data = this.getData(); if (!data) throw new Error('Data is missing'); + + this.#persistedData.setValue(data); + this.#currentData.setValue(data); + this.saveComplete(data); } @@ -273,11 +279,7 @@ export class UmbDocumentWorkspaceContext const unique = this.getEntityId(); if (!unique) throw new Error('Unique is missing'); - //if (!this.#variantManagerContext) throw new Error('Variant manager context is missing'); - - //this.#variantManagerContext.unpublish(unique); - alert('not implemented'); - throw new Error('Not implemented'); + new UmbUnpublishDocumentEntityAction(this, '', unique, ''); } async delete() { From 9dc2de7e1a6394d34b1f7778b32e116590f7d241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 11:56:57 +0100 Subject: [PATCH 72/98] unpublish using pick variants --- .../documents/workspace/document-workspace.context.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 1537f70be0..b171c5bd49 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -276,10 +276,11 @@ export class UmbDocumentWorkspaceContext } public async unpublish() { + const variantIds = await this.#pickVariantsForAction('unpublish'); const unique = this.getEntityId(); - - if (!unique) throw new Error('Unique is missing'); - new UmbUnpublishDocumentEntityAction(this, '', unique, ''); + if (variantIds.length && unique) { + await this.publishingRepository.unpublish(unique, variantIds); + } } async delete() { From 23affcd075b57db68ee41c78c87ecc6c06da6068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 11:57:44 +0100 Subject: [PATCH 73/98] unpublish using entity action --- .../documents/workspace/document-workspace.context.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index b171c5bd49..1537f70be0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -276,11 +276,10 @@ export class UmbDocumentWorkspaceContext } public async unpublish() { - const variantIds = await this.#pickVariantsForAction('unpublish'); const unique = this.getEntityId(); - if (variantIds.length && unique) { - await this.publishingRepository.unpublish(unique, variantIds); - } + + if (!unique) throw new Error('Unique is missing'); + new UmbUnpublishDocumentEntityAction(this, '', unique, ''); } async delete() { From aab05c2977ee2434dd58de2ce4ac36792b7f373c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 12:34:38 +0100 Subject: [PATCH 74/98] TODOs --- .../documents/documents/entity-actions/publish.action.ts | 1 + .../documents/documents/entity-actions/unpublish.action.ts | 1 + .../documents/modals/pick-document-variant-modal.controller.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts index aa90aec2e2..0cd4fa2219 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts @@ -20,6 +20,7 @@ export class UmbPublishDocumentEntityAction extends UmbEntityActionBase diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts index 43af363392..509b5f1c97 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts @@ -20,6 +20,7 @@ export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts index 9aedbb9009..c953322f64 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts @@ -30,6 +30,7 @@ export class UmbPickDocumentVariantModalController extends UmbBaseController { // TODO: What do to when there is no options? } + // TODO: Maybe move this to modal [NL] if (selected.length === 0) { // TODO: Make it possible to use consume context without callback. [NL] const ctrl = this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (appLanguageContext) => {}); From 1fafc23a327a934f5310f8d9f00d80ffbd496281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 12:47:31 +0100 Subject: [PATCH 75/98] move fallback to app language logic to modal --- .../pick-document-variant-modal.controller.ts | 13 ------ .../document-variant-picker-modal.element.ts | 23 ++++++++-- .../workspace/document-workspace.context.ts | 45 ++++++++++++++++--- 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts index c953322f64..696263afd4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts @@ -30,19 +30,6 @@ export class UmbPickDocumentVariantModalController extends UmbBaseController { // TODO: What do to when there is no options? } - // TODO: Maybe move this to modal [NL] - if (selected.length === 0) { - // TODO: Make it possible to use consume context without callback. [NL] - const ctrl = this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (appLanguageContext) => {}); - const context = await ctrl.asPromise(); - const appCulture = context.getAppCulture(); - // If the app language is one of the options, select it by default: - if (appCulture && modalData.options.some((o) => o.language.unique === appCulture)) { - selected.push(new UmbVariantId(appCulture, null)); - } - ctrl.destroy(); - } - const modalContext = modalManagerContext.open(UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, { data: modalData, // We need to turn the selected variant ids into strings for them to be serializable to the value state, in other words the value of a modal cannot hold class instances: diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts index 7d60157b54..51cc60c9e6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts @@ -27,11 +27,26 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement< connectedCallback(): void { super.connectedCallback(); - this.#selectionManager.setSelectable(true); - this.#selectionManager.setMultiple(true); + this.#setInitialSelection(); + } - // Make sure all mandatory variants are selected when not in unpublish mode - // TODO: Currently only supports culture variants, not segment variants, but as well our Selection Manager also only supports a single string value pr. selection... [NL] + async #setInitialSelection() { + const selected = this.value?.selection ?? []; + + if (selected.length === 0) { + // TODO: Make it possible to use consume context without callback. [NL] + const ctrl = this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (appLanguageContext) => {}); + const context = await ctrl.asPromise(); + const appCulture = context.getAppCulture(); + // If the app language is one of the options, select it by default: + if (appCulture && modalData.options.some((o) => o.language.unique === appCulture)) { + selected.push(new UmbVariantId(appCulture, null)); + } + ctrl.destroy(); + } + + this.#selectionManager.setMultiple(true); + this.#selectionManager.setSelectable(true); this.#selectionManager.setSelection(this.value?.selection ?? []); if (this.data?.type !== 'unpublish') { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 1537f70be0..78c8a90322 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -5,12 +5,9 @@ import { UmbDocumentDetailRepository } from '../repository/index.js'; import type { UmbDocumentDetailModel, UmbDocumentVariantModel, UmbDocumentVariantOptionModel } from '../types.js'; import { umbPickDocumentVariantModal, type UmbDocumentVariantPickerModalType } from '../modals/index.js'; import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js'; +import { UmbUnpublishDocumentEntityAction } from '../entity-actions/unpublish.action.js'; import { UMB_DOCUMENT_WORKSPACE_ALIAS } from './manifests.js'; -import { - type UmbObjectWithVariantProperties, - UmbVariantId, - variantPropertiesObjectToString, -} from '@umbraco-cms/backoffice/variant'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; import { UmbEditableWorkspaceContextBase, @@ -21,6 +18,7 @@ import { import { appendToFrozenArray, combineObservables, + naiveObjectComparison, partialUpdateFrozenArray, UmbArrayState, UmbObjectState, @@ -28,7 +26,6 @@ import { import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbUnpublishDocumentEntityAction } from '../entity-actions/unpublish.action.js'; type EntityType = UmbDocumentDetailModel; export class UmbDocumentWorkspaceContext @@ -212,11 +209,45 @@ export class UmbDocumentWorkspaceContext } } + #calculateChangedVariants() { + const persisted = this.#persistedData.getValue(); + if (!persisted) throw new Error('Persisted data is missing'); + const current = this.#currentData.getValue(); + if (!persisted) throw new Error('Current data is missing'); + + const changedVariants = current?.variants.map((variant) => { + const persistedVariant = persisted.variants.find((x) => UmbVariantId.Create(variant).compare(x)); + return { + culture: variant.culture, + segment: variant.segment, + equal: persistedVariant ? naiveObjectComparison(variant, persistedVariant) : false, + }; + }); + + const changedProperties = current?.values.map((value) => { + const persistedValues = persisted.values.find((x) => UmbVariantId.Create(value).compare(x)); + return { + culture: value.culture, + segment: value.segment, + equal: persistedValues ? naiveObjectComparison(value, persistedValues) : false, + }; + }); + + // calculate the variantIds of those who either have a change in properties or in variants: + return ( + changedVariants + ?.concat(changedProperties ?? []) + .filter((x) => x.equal === false) + .map((x) => new UmbVariantId(x.culture, x.segment)) ?? [] + ); + } + async #pickVariantsForAction(type: UmbDocumentVariantPickerModalType): Promise { const activeVariants = this.splitView.getActiveVariants(); // TODO: Picked variants should include the ones that has been changed (but not jet saved) this requires some more awareness about the state of runtime data. [NL] - const selected = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant)); + const activeVariantIds = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant)); + const selected = activeVariantIds.concat(this.#calculateChangedVariants()); const options = await firstValueFrom(this.variantOptions); // If there is only one variant, we don't need to open the modal. From d3be1e62717f17e1eff2235cd1f5753d7ccc82d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 12:59:43 +0100 Subject: [PATCH 76/98] clean --- .../documents/documents/entity-actions/publish.action.ts | 1 + .../documents/modals/pick-document-variant-modal.controller.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts index 0cd4fa2219..f2eb377ec6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts @@ -26,6 +26,7 @@ export class UmbPublishDocumentEntityAction extends UmbEntityActionBase option.variant && (option.variant.state === UmbDocumentVariantState.DRAFT || + option.variant.state === UmbDocumentVariantState.PUBLISHED || option.variant.state === UmbDocumentVariantState.PUBLISHED_PENDING_CHANGES), ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts index 696263afd4..a5a03b7463 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/pick-document-variant-modal.controller.ts @@ -7,7 +7,6 @@ import { import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export interface UmbPickDocumentVariantModalArgs { From 34dcaaecb2d869a578dbaf32722109352fde5453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 13:08:58 +0100 Subject: [PATCH 77/98] missing call to execute() --- .../documents/documents/workspace/document-workspace.context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 78c8a90322..62b353800a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -310,7 +310,7 @@ export class UmbDocumentWorkspaceContext const unique = this.getEntityId(); if (!unique) throw new Error('Unique is missing'); - new UmbUnpublishDocumentEntityAction(this, '', unique, ''); + new UmbUnpublishDocumentEntityAction(this, '', unique, '').execute(); } async delete() { From c83e4368cfcfa93cec550d3f6b998be6c43350e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 13:31:56 +0100 Subject: [PATCH 78/98] ensureVariantDataFor + correct calculateChangedVariants --- .../workspace/document-workspace.context.ts | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 62b353800a..155c66f745 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -108,6 +108,7 @@ export class UmbDocumentWorkspaceContext if (!data) return undefined; this.setIsNew(true); + this.#persistedData.setValue(undefined); this.#currentData.setValue(data); return data || undefined; } @@ -154,6 +155,7 @@ export class UmbDocumentWorkspaceContext variantId ? (x) => variantId.compare(x) : () => true, ); this.#currentData.update({ variants }); + this.#ensureVariantDataFor(variantId ?? UmbVariantId.CreateInvariant()); } setTemplate(templateUnique: string) { @@ -193,7 +195,7 @@ export class UmbDocumentWorkspaceContext value: UmbDocumentValueModel, variantId?: UmbVariantId, ) { - if (!variantId) throw new Error('VariantId is missing'); + variantId ??= UmbVariantId.CreateInvariant(); const entry = { ...variantId.toObject(), alias, value }; const currentData = this.getData(); @@ -205,18 +207,18 @@ export class UmbDocumentWorkspaceContext ); this.#currentData.update({ values }); - // TODO: Ensure variant object.. + // If it turns out to become a performance problem to ensure this all the time, then we should move this type of logic to the data-source + this.#ensureVariantDataFor(variantId); } } #calculateChangedVariants() { const persisted = this.#persistedData.getValue(); - if (!persisted) throw new Error('Persisted data is missing'); const current = this.#currentData.getValue(); - if (!persisted) throw new Error('Current data is missing'); + if (!current) throw new Error('Current data is missing'); const changedVariants = current?.variants.map((variant) => { - const persistedVariant = persisted.variants.find((x) => UmbVariantId.Create(variant).compare(x)); + const persistedVariant = persisted?.variants.find((x) => UmbVariantId.Create(variant).compare(x)); return { culture: variant.culture, segment: variant.segment, @@ -225,7 +227,7 @@ export class UmbDocumentWorkspaceContext }); const changedProperties = current?.values.map((value) => { - const persistedValues = persisted.values.find((x) => UmbVariantId.Create(value).compare(x)); + const persistedValues = persisted?.values.find((x) => UmbVariantId.Create(value).compare(x)); return { culture: value.culture, segment: value.segment, @@ -242,6 +244,26 @@ export class UmbDocumentWorkspaceContext ); } + #ensureVariantDataFor(variantId: UmbVariantId) { + const currentData = this.getData(); + if (!currentData) throw new Error('Data is missing'); + const variant = currentData.variants.find((x) => variantId.compare(x)); + const newVariants = appendToFrozenArray( + currentData.variants, + { + state: null, + name: '', + publishDate: null, + createDate: null, + updateDate: null, + ...variantId.toObject(), + ...variant, + }, + (x) => variantId.compare(x), + ); + this.#currentData.update({ variants: newVariants }); + } + async #pickVariantsForAction(type: UmbDocumentVariantPickerModalType): Promise { const activeVariants = this.splitView.getActiveVariants(); From 34cc1ddb863032a0608207d24c3dc6473b306aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 13:34:39 +0100 Subject: [PATCH 79/98] todo comment --- .../documents/workspace/document-workspace.context.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 155c66f745..71c8ddaf86 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -155,6 +155,7 @@ export class UmbDocumentWorkspaceContext variantId ? (x) => variantId.compare(x) : () => true, ); this.#currentData.update({ variants }); + // TODO: We should move this type of logic to the act of saving [NL] this.#ensureVariantDataFor(variantId ?? UmbVariantId.CreateInvariant()); } @@ -207,7 +208,7 @@ export class UmbDocumentWorkspaceContext ); this.#currentData.update({ values }); - // If it turns out to become a performance problem to ensure this all the time, then we should move this type of logic to the data-source + // TODO: We should move this type of logic to the act of saving [NL] this.#ensureVariantDataFor(variantId); } } From 00d6fd785fdcd36be898b265dfee2f816902ee7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 13:45:02 +0100 Subject: [PATCH 80/98] updateVariantData --- .../variant-selector/variant-selector.element.ts | 2 +- .../documents/workspace/document-workspace.context.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts index 1bc79d33fb..d3a961829c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/variant-selector/variant-selector.element.ts @@ -201,7 +201,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { render() { return html` - + ${ this._variants?.length ? html` diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 71c8ddaf86..c8f10f7792 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -148,6 +148,7 @@ export class UmbDocumentWorkspaceContext } setName(name: string, variantId?: UmbVariantId) { + /* const oldVariants = this.#currentData.getValue()?.variants || []; const variants = partialUpdateFrozenArray( oldVariants, @@ -155,8 +156,9 @@ export class UmbDocumentWorkspaceContext variantId ? (x) => variantId.compare(x) : () => true, ); this.#currentData.update({ variants }); + */ // TODO: We should move this type of logic to the act of saving [NL] - this.#ensureVariantDataFor(variantId ?? UmbVariantId.CreateInvariant()); + this.#updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); } setTemplate(templateUnique: string) { @@ -209,7 +211,7 @@ export class UmbDocumentWorkspaceContext this.#currentData.update({ values }); // TODO: We should move this type of logic to the act of saving [NL] - this.#ensureVariantDataFor(variantId); + this.#updateVariantData(variantId); } } @@ -245,7 +247,7 @@ export class UmbDocumentWorkspaceContext ); } - #ensureVariantDataFor(variantId: UmbVariantId) { + #updateVariantData(variantId: UmbVariantId, update?: Partial) { const currentData = this.getData(); if (!currentData) throw new Error('Data is missing'); const variant = currentData.variants.find((x) => variantId.compare(x)); @@ -259,6 +261,7 @@ export class UmbDocumentWorkspaceContext updateDate: null, ...variantId.toObject(), ...variant, + ...update, }, (x) => variantId.compare(x), ); From eb670a3b3cec1b5983178551846c4d1f58827109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 13:46:41 +0100 Subject: [PATCH 81/98] fix modal --- .../variant-picker/document-variant-picker-modal.element.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts index 51cc60c9e6..e6cd88fb31 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts @@ -7,6 +7,8 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; @customElement('umb-document-variant-picker-modal') export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement< @@ -39,8 +41,8 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement< const context = await ctrl.asPromise(); const appCulture = context.getAppCulture(); // If the app language is one of the options, select it by default: - if (appCulture && modalData.options.some((o) => o.language.unique === appCulture)) { - selected.push(new UmbVariantId(appCulture, null)); + if (appCulture && this.data?.options.some((o) => o.language.unique === appCulture)) { + selected.push(new UmbVariantId(appCulture, null).toString()); } ctrl.destroy(); } From a9c90563b967375c215d92aa9de40c9f7c259279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 14:17:10 +0100 Subject: [PATCH 82/98] restructure package --- .../{ => component}/modal-element.element.ts | 0 .../core/modal/{ => component}/modal.element.ts | 4 ++-- .../modal/{ => context}/modal-manager.context.ts | 2 +- .../core/modal/{ => context}/modal.context.ts | 2 +- .../src/packages/core/modal/index.ts | 16 ++++++++-------- .../modal-route-registration.controller.ts | 2 +- .../modal-route-registration.ts | 6 +++--- .../modal/token/data-type-picker-modal.token.ts | 2 +- .../src/packages/core/modal/token/modal-token.ts | 2 +- .../core/modal/{modal.interfaces.ts => types.ts} | 0 10 files changed, 18 insertions(+), 18 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/core/modal/{ => component}/modal-element.element.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/core/modal/{ => component}/modal.element.ts (97%) rename src/Umbraco.Web.UI.Client/src/packages/core/modal/{ => context}/modal-manager.context.ts (97%) rename src/Umbraco.Web.UI.Client/src/packages/core/modal/{ => context}/modal.context.ts (98%) rename src/Umbraco.Web.UI.Client/src/packages/core/modal/{ => route-registration}/modal-route-registration.controller.ts (99%) rename src/Umbraco.Web.UI.Client/src/packages/core/modal/{ => route-registration}/modal-route-registration.ts (95%) rename src/Umbraco.Web.UI.Client/src/packages/core/modal/{modal.interfaces.ts => types.ts} (100%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-element.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal-element.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-element.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal-element.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts similarity index 97% rename from src/Umbraco.Web.UI.Client/src/packages/core/modal/modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts index 7fcefa91c8..a7bb8308fd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts @@ -1,5 +1,5 @@ -import type { UmbModalContext } from './modal.context.js'; -import { UMB_MODAL_CONTEXT } from './modal.context.js'; +import type { UmbModalContext } from '../context/modal.context.js'; +import { UMB_MODAL_CONTEXT } from '../context/modal.context.js'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts similarity index 97% rename from src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-manager.context.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts index f76066bbc3..6edd817eac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts @@ -1,4 +1,4 @@ -import type { UmbModalToken } from './token/modal-token.js'; +import type { UmbModalToken } from '../token/modal-token.js'; import { UmbModalContext, type UmbModalContextClassArgs } from './modal.context.js'; import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui'; import { UmbBasicState, appendToFrozenArray } from '@umbraco-cms/backoffice/observable-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts similarity index 98% rename from src/Umbraco.Web.UI.Client/src/packages/core/modal/modal.context.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts index c9c9fbeb74..a27f084725 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts @@ -1,5 +1,5 @@ import type { UmbModalConfig, UmbModalType } from './modal-manager.context.js'; -import { UmbModalToken } from './token/modal-token.js'; +import { UmbModalToken } from '../token/modal-token.js'; import type { IRouterSlot } from '@umbraco-cms/backoffice/external/router-slot'; import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui'; import { UmbId } from '@umbraco-cms/backoffice/id'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/index.ts index d2b5c18109..02ecca3dbf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/index.ts @@ -1,10 +1,10 @@ -import './modal.element.js'; +import './component/modal.element.js'; -export * from './modal-manager.context.js'; -export * from './modal.context.js'; -export * from './modal-route-registration.js'; -export * from './modal-route-registration.controller.js'; +export * from './context/modal-manager.context.js'; +export * from './context/modal.context.js'; +export * from './route-registration/modal-route-registration.js'; +export * from './route-registration/modal-route-registration.controller.js'; export * from './token/index.js'; -export * from './modal.interfaces.js'; -export * from './modal-element.element.js'; -export * from './modal.element.js'; +export * from './types.js'; +export * from './component/modal-element.element.js'; +export * from './component/modal.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-route-registration.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts similarity index 99% rename from src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-route-registration.controller.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts index 622839d5b4..d19afe5dba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-route-registration.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts @@ -1,5 +1,5 @@ import { UmbModalRouteRegistration } from './modal-route-registration.js'; -import type { UmbModalToken } from './token/index.js'; +import type { UmbModalToken } from '../token/index.js'; import { UMB_ROUTE_CONTEXT } from '@umbraco-cms/backoffice/router'; import type { UmbController, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-route-registration.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.ts similarity index 95% rename from src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-route-registration.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.ts index 9cf6f10e56..25310bbca8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal-route-registration.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.ts @@ -1,6 +1,6 @@ -import type { UmbModalContext } from './modal.context.js'; -import type { UmbModalConfig, UmbModalManagerContext } from './modal-manager.context.js'; -import type { UmbModalToken } from './token/modal-token.js'; +import type { UmbModalContext } from '../context/modal.context.js'; +import type { UmbModalConfig, UmbModalManagerContext } from '../context/modal-manager.context.js'; +import type { UmbModalToken } from '../token/modal-token.js'; import type { IRouterSlot } from '@umbraco-cms/backoffice/external/router-slot'; import { encodeFolderName } from '@umbraco-cms/backoffice/router'; import { UmbId } from '@umbraco-cms/backoffice/id'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/data-type-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/data-type-picker-modal.token.ts index f128ceb2ad..5c0a42653f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/data-type-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/data-type-picker-modal.token.ts @@ -1,4 +1,4 @@ -import type { UmbPickerModalValue, UmbTreePickerModalData } from '../modal.interfaces.js'; +import type { UmbPickerModalValue, UmbTreePickerModalData } from '../types.js'; import { UmbModalToken } from './modal-token.js'; import type { UmbUniqueTreeItemModel } from '@umbraco-cms/backoffice/tree'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts index 97f893fffb..8d45fde4b9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts @@ -1,4 +1,4 @@ -import type { UmbModalConfig } from '../modal-manager.context.js'; +import type { UmbModalConfig } from '../context/modal-manager.context.js'; export interface UmbModalTokenDefaults { modal?: UmbModalConfig; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/modal.interfaces.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/types.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/modal/modal.interfaces.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/modal/types.ts From 9d6c861f1718316882c9db12a0f7cd5c608196c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 14:18:32 +0100 Subject: [PATCH 83/98] correct to using mergeObservables --- .../documents/workspace/document-workspace.context.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index fc20f8d222..049eef1b58 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -17,9 +17,8 @@ import { } from '@umbraco-cms/backoffice/workspace'; import { appendToFrozenArray, - combineObservables, + mergeObservables, naiveObjectComparison, - partialUpdateFrozenArray, UmbArrayState, UmbObjectState, } from '@umbraco-cms/backoffice/observable-api'; @@ -56,7 +55,7 @@ export class UmbDocumentWorkspaceContext readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.documentType.unique); readonly contentTypeHasCollection = this.#currentData.asObservablePart((data) => !!data?.documentType.collection); readonly variants = this.#currentData.asObservablePart((data) => data?.variants ?? []); - readonly variantOptions = combineObservables([this.variants, this.languages], ([variants, languages]) => { + readonly variantOptions = mergeObservables([this.variants, this.languages], ([variants, languages]) => { return languages.map((language) => { return { variant: variants.find((x) => x.culture === language.unique), From 2b0fa492c980f9a9cd056753109bdf4e95d30e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 14:20:08 +0100 Subject: [PATCH 84/98] also use merge Observables --- .../packages/media/media/workspace/media-workspace.context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts index 3415c5a80b..4aa4d0997f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts @@ -12,7 +12,7 @@ import { } from '@umbraco-cms/backoffice/workspace'; import { appendToFrozenArray, - combineObservables, + mergeObservables, partialUpdateFrozenArray, UmbArrayState, UmbObjectState, @@ -47,7 +47,7 @@ export class UmbMediaWorkspaceContext readonly contentTypeCollection = this.#currentData.asObservablePart((data) => data?.mediaType.collection); readonly variants = this.#currentData.asObservablePart((data) => data?.variants || []); - readonly variantOptions = combineObservables([this.variants, this.languages], ([variants, languages]) => { + readonly variantOptions = mergeObservables([this.variants, this.languages], ([variants, languages]) => { return languages.map((language) => { return { variant: variants.find((x) => x.culture === language.unique), From fe7e64baf569be151be6dd90b3182ecad5d2cc51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 14:21:36 +0100 Subject: [PATCH 85/98] empty file --- .../core/modal/common/confirm/confirm-modal.controller.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.controller.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.controller.ts new file mode 100644 index 0000000000..e69de29bb2 From 7ad25839c8ef42e5f4eedc8a00f640bfc38b414a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 15:02:47 +0100 Subject: [PATCH 86/98] implement getContext method --- .../src/libs/class-api/class.mixin.ts | 21 +++++++++++++++++++ .../consume/context-consumer.controller.ts | 2 +- .../src/libs/element-api/element.mixin.ts | 21 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts index 465d7e3460..a755cebc59 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts @@ -65,6 +65,16 @@ declare class UmbClassMixinDeclaration extends EventTarget implements UmbClassMi callback: UmbContextCallback, ): UmbContextConsumerController; + /** + * @description Retrieve a context. Notice this is a one time retrieving of a context, meaning if you expect this to be up to date with reality you should instead use the consumeContext method. + * @param {string} contextAlias + * @return {Promise} A Promise with the reference to the Context Api Instance + * @memberof UmbClassMixin + */ + getContext( + alias: string | UmbContextToken, + ): Promise; + hasController(controller: UmbController): boolean; getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[]; addController(controller: UmbController): void; @@ -129,6 +139,17 @@ export const UmbClassMixin = (superClass: T) => { return new UmbContextConsumerController(this, contextAlias, callback); } + async getContext( + contextAlias: string | UmbContextToken, + ): Promise { + const controller = new UmbContextConsumerController(this, contextAlias); + const promise = controller.asPromise().then((result) => { + controller.destroy(); + return result; + }); + return promise; + } + public destroy(): void { if (this._host) { this._host.removeController(this); diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts index fcafe8efb8..4faf16708d 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts @@ -17,7 +17,7 @@ export class UmbContextConsumerController, - callback: UmbContextCallback, + callback?: UmbContextCallback, ) { super(host.getHostElement(), contextAlias, callback); this.#host = host; diff --git a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts index dbf72e6a23..feb052e9ab 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts @@ -32,6 +32,9 @@ export declare class UmbElement extends UmbControllerHostElement { alias: string | UmbContextToken, callback: UmbContextCallback, ): UmbContextConsumerController; + getContext( + alias: string | UmbContextToken, + ): Promise; /** * Use the UmbLocalizeController to localize your element. * @see UmbLocalizationController @@ -86,6 +89,24 @@ export const UmbElementMixin = (superClass: T) return new UmbContextConsumerController(this, alias, callback); } + /** + * @description Setup a subscription for a context. The callback is called when the context is resolved. + * @param {string} contextAlias + * @param {method} callback Callback method called when context is resolved. + * @return {UmbContextConsumerController} Reference to a Context Consumer Controller instance + * @memberof UmbElementMixin + */ + async getContext( + contextAlias: string | UmbContextToken, + ): Promise { + const controller = new UmbContextConsumerController(this, contextAlias); + const promise = controller.asPromise().then((result) => { + controller.destroy(); + return result; + }); + return promise; + } + destroy(): void { super.destroy(); (this.localize as any) = undefined; From d9760a23f4f78d5422a3fb94e1ad3d268a3bcf29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 15:02:56 +0100 Subject: [PATCH 87/98] confirm modal method --- .../block-grid-area-config-entry.context.ts | 21 +++--- .../input-block-type.element.ts | 70 ++++++++----------- .../block/context/block-entry.context.ts | 22 +++--- .../confirm/confirm-modal.controller.ts | 30 ++++++++ .../core/modal/context/modal.context.ts | 2 + .../src/packages/core/modal/index.ts | 1 + 6 files changed, 81 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/block-grid-area-config-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/block-grid-area-config-entry.context.ts index c138fb6300..3067cfc101 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/block-grid-area-config-entry.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/block-grid-area-config-entry.context.ts @@ -9,7 +9,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; -import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; export class UmbBlockGridAreaConfigEntryContext extends UmbContextBase implements UmbBlockGridScalableContext @@ -86,19 +86,14 @@ export class UmbBlockGridAreaConfigEntryContext ); } - requestDelete() { - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => { - const modalContext = modalManager.open(UMB_CONFIRM_MODAL, { - data: { - headline: `Delete ${this.alias}`, - content: 'Are you sure you want to delete this Area?', - confirmLabel: 'Delete', - color: 'danger', - }, - }); - await modalContext.onSubmit(); - this.delete(); + async requestDelete() { + await umbConfirmModal(this, { + headline: `Delete ${this.alias}`, + content: 'Are you sure you want to delete this Area?', + confirmLabel: 'Delete', + color: 'danger', }); + this.delete(); } public delete() { if (!this.#areaKey || !this.#propertyContext) return; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts index 8caac68e41..31593e2b2b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts @@ -1,8 +1,8 @@ import type { UmbBlockTypeBaseModel } from '../../types.js'; import { - UMB_CONFIRM_MODAL, UMB_DOCUMENT_TYPE_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT, + umbConfirmModal, } from '@umbraco-cms/backoffice/modal'; import '../block-type-card/index.js'; import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit'; @@ -42,35 +42,33 @@ export class UmbInputBlockTypeElement< }); } - create() { - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => { - if (modalManager) { - // TODO: Make as mode for the Picker Modal, so the click to select immediately submits the modal(And in that mode we do not want to see a Submit button). - const modalContext = modalManager.open(UMB_DOCUMENT_TYPE_PICKER_MODAL, { - data: { - hideTreeRoot: true, - multiple: false, - pickableFilter: (docType) => - // Only pick elements: - docType.isElement && - // Prevent picking the an already used element type: - this.#filter && - this.#filter.find((x) => x.contentElementTypeKey === docType.unique) === undefined, - }, - }); + async create() { + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - const modalValue = await modalContext?.onSubmit(); - const selectedElementType = modalValue.selection[0]; - - if (selectedElementType) { - this.dispatchEvent(new CustomEvent('create', { detail: { contentElementTypeKey: selectedElementType } })); - } - } + // TODO: Make as mode for the Picker Modal, so the click to select immediately submits the modal(And in that mode we do not want to see a Submit button). + const modalContext = modalManager.open(UMB_DOCUMENT_TYPE_PICKER_MODAL, { + data: { + hideTreeRoot: true, + multiple: false, + pickableFilter: (docType) => + // Only pick elements: + docType.isElement && + // Prevent picking the an already used element type: + this.#filter && + this.#filter.find((x) => x.contentElementTypeKey === docType.unique) === undefined, + }, }); + + const modalValue = await modalContext?.onSubmit(); + const selectedElementType = modalValue.selection[0]; + + if (selectedElementType) { + this.dispatchEvent(new CustomEvent('create', { detail: { contentElementTypeKey: selectedElementType } })); + } } deleteItem(contentElementTypeKey: string) { - this.value = this._items.filter((x) => x.contentElementTypeKey !== contentElementTypeKey); + this.value = this.value.filter((x) => x.contentElementTypeKey !== contentElementTypeKey); this.dispatchEvent(new UmbChangeEvent()); } @@ -78,20 +76,14 @@ export class UmbInputBlockTypeElement< return undefined; } - #onRequestDelete(item: BlockType) { - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => { - const modalContext = modalManager.open(UMB_CONFIRM_MODAL, { - data: { - color: 'danger', - headline: `Remove [TODO: Get name]?`, - content: 'Are you sure you want to remove this block type?', - confirmLabel: 'Remove', - }, - }); - - await modalContext?.onSubmit(); - this.deleteItem(item.contentElementTypeKey); + async #onRequestDelete(item: BlockType) { + await umbConfirmModal(this, { + color: 'danger', + headline: `Remove [TODO: Get name]?`, + content: 'Are you sure you want to remove this block type?', + confirmLabel: 'Remove', }); + this.deleteItem(item.contentElementTypeKey); } render() { @@ -109,7 +101,7 @@ export class UmbInputBlockTypeElement< .href="${this.workspacePath}/edit/${block.contentElementTypeKey}" .contentElementTypeKey=${block.contentElementTypeKey}> - + this.#onRequestDelete(block)} label="Remove block"> diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts index 323889f06a..2cdac3315d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts @@ -7,7 +7,7 @@ import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbNumberState, UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api'; import { encodeFilePath } from '@umbraco-cms/backoffice/utils'; -import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; @@ -353,20 +353,16 @@ export abstract class UmbBlockEntryContext< window.location.href = this.#generateWorkspaceEditSettingsPath(this.#workspacePath.value); } - requestDelete() { - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => { - const modalContext = modalManager.open(UMB_CONFIRM_MODAL, { - data: { - headline: `Delete ${this.getLabel()}`, - content: 'Are you sure you want to delete this [INSERT BLOCK TYPE NAME]?', - confirmLabel: 'Delete', - color: 'danger', - }, - }); - await modalContext.onSubmit(); - this.delete(); + async requestDelete() { + await umbConfirmModal(this, { + headline: `Delete ${this.getLabel()}`, + content: 'Are you sure you want to delete this [INSERT BLOCK TYPE NAME]?', + confirmLabel: 'Delete', + color: 'danger', }); + this.delete(); } + public delete() { if (!this._entries) return; const contentUdi = this._layout.value?.contentUdi; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.controller.ts index e69de29bb2..1fa482ab54 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.controller.ts @@ -0,0 +1,30 @@ +import { UMB_CONFIRM_MODAL, type UmbConfirmModalData } from '../../token/confirm-modal.token.js'; +import { UMB_MODAL_MANAGER_CONTEXT } from '../../context/modal-manager.context.js'; +import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export interface UmbConfirmModalArgs extends UmbConfirmModalData {} + +export class UmbConfirmModalController extends UmbBaseController { + async open(args: UmbConfirmModalArgs): Promise { + const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + + const modalContext = modalManagerContext.open(UMB_CONFIRM_MODAL, { + data: args, + }); + + await modalContext.onSubmit().catch(() => { + this.destroy(); + }); + + // This is a one time off, so we can destroy our selfs. + this.destroy(); + + // Map back into UmbVariantId instances: + return; + } +} + +export function umbConfirmModal(host: UmbControllerHost, args: UmbConfirmModalArgs) { + return new UmbConfirmModalController(host).open(args); +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts index a27f084725..227ba41e63 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts @@ -81,6 +81,7 @@ export class UmbModalContext Date: Tue, 27 Feb 2024 15:35:49 +0100 Subject: [PATCH 88/98] remove empty file --- .../documents/modals/publish-document-modal.function._ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-document-modal.function._ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-document-modal.function._ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-document-modal.function._ts deleted file mode 100644 index e69de29bb2..0000000000 From 2ff1f4930c07642db1c9616917445d2e833a8cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 15:36:33 +0100 Subject: [PATCH 89/98] update confirm modal implementations --- ...ultiple-color-picker-item-input.element.ts | 31 +++------ ...input-multiple-text-string-item.element.ts | 31 +++------ .../common/delete/delete.action.ts | 31 ++------- .../common/trash/trash.action.ts | 31 ++------- ...sion-table-action-column-layout.element.ts | 26 ++----- .../core/picker-input/picker-input.context.ts | 41 +++-------- .../delete-folder/delete-folder.action.ts | 32 +++------ .../dashboard-redirect-management.element.ts | 68 +++++++------------ ...pe-workspace-view-edit-property.element.ts | 27 ++------ ...cument-type-workspace-view-edit.element.ts | 19 ++---- .../log-viewer-search-input.element.ts | 22 +++--- ...pe-workspace-view-edit-property.element.ts | 22 ++---- .../media-type-workspace-view-edit.element.ts | 17 ++--- .../packages-created-overview.element.ts | 26 ++----- ...lled-packages-section-view-item.element.ts | 22 ++---- .../views/section-view-examine-indexers.ts | 40 ++++------- .../dashboard-published-status.element.ts | 63 ++++++----------- .../delete/delete.action.ts | 31 ++------- .../disable/disable-user.action.ts | 26 ++----- .../enable/enable-user.action.ts | 24 ++----- .../unlock/unlock-user.action.ts | 24 ++----- .../delete/delete.action.ts | 31 ++------- 22 files changed, 185 insertions(+), 500 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts index ad85c0695d..b64bb63ac9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts @@ -1,8 +1,7 @@ import { css, html, nothing, customElement, property, query, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import type { UUIColorPickerElement, UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import { UmbChangeEvent, UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -38,32 +37,18 @@ export class UmbMultipleColorPickerItemInputElement extends FormControlMixin(Umb @query('#color') protected _colorPicker!: UUIColorPickerElement; - private _modalContext?: UmbModalManagerContext; - @property({ type: Boolean }) showLabels = true; - constructor() { - super(); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this._modalContext = instance; - }); - } - - #onDelete() { - const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: `${this.localize.term('actions_delete')} ${this.value || ''}`, - content: this.localize.term('content_nestedContentDeleteItem'), - color: 'danger', - confirmLabel: this.localize.term('actions_delete'), - }, + async #onDelete() { + await umbConfirmModal(this, { + headline: `${this.localize.term('actions_delete')} ${this.value || ''}`, + content: this.localize.term('content_nestedContentDeleteItem'), + color: 'danger', + confirmLabel: this.localize.term('actions_delete'), }); - modalContext?.onSubmit().then(() => { - this.dispatchEvent(new UmbDeleteEvent()); - }); + this.dispatchEvent(new UmbDeleteEvent()); } #onLabelInput(event: UUIInputEvent) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts index ef72181645..f606197309 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts @@ -1,8 +1,7 @@ import { css, html, nothing, customElement, property, query } from '@umbraco-cms/backoffice/external/lit'; import type { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import { UmbChangeEvent, UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -32,29 +31,15 @@ export class UmbInputMultipleTextStringItemElement extends FormControlMixin(UmbL @query('#input') protected _input?: UUIInputElement; - private _modalContext?: UmbModalManagerContext; - - constructor() { - super(); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this._modalContext = instance; - }); - } - - #onDelete() { - const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: `Delete ${this.value || 'item'}`, - content: 'Are you sure you want to delete this item?', - color: 'danger', - confirmLabel: 'Delete', - }, + async #onDelete() { + await umbConfirmModal(this, { + headline: `Delete ${this.value || 'item'}`, + content: 'Are you sure you want to delete this item?', + color: 'danger', + confirmLabel: 'Delete', }); - modalContext?.onSubmit().then(() => { - this.dispatchEvent(new UmbDeleteEvent()); - }); + this.dispatchEvent(new UmbDeleteEvent()); } #onInput(event: UUIInputEvent) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.ts index ff428c2f09..68f4571c56 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.ts @@ -1,39 +1,22 @@ import { UmbEntityActionBase } from '../../entity-action.js'; -import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbModalManagerContext} from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import type { UmbDetailRepository, UmbItemRepository } from '@umbraco-cms/backoffice/repository'; export class UmbDeleteEntityAction< T extends UmbDetailRepository & UmbItemRepository, > extends UmbEntityActionBase { - #modalManager?: UmbModalManagerContext; - - constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { - super(host, repositoryAlias, unique, entityType); - - new UmbContextConsumerController(this._host, UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalManager = instance; - }); - } - async execute() { - if (!this.repository || !this.#modalManager) return; + if (!this.repository) return; // TOOD: add back when entity actions can support multiple repositories //const { data } = await this.repository.requestItems([this.unique]); - const modalContext = this.#modalManager.open(UMB_CONFIRM_MODAL, { - data: { - headline: `Delete`, - content: 'Are you sure you want to delete this item?', - color: 'danger', - confirmLabel: 'Delete', - }, + await umbConfirmModal(this, { + headline: `Delete`, + content: 'Are you sure you want to delete this item?', + color: 'danger', + confirmLabel: 'Delete', }); - - await modalContext.onSubmit(); await this.repository?.delete(this.unique); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts index eb51e0520c..d46f1f5d22 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts @@ -1,23 +1,10 @@ import { UmbEntityActionBase } from '../../entity-action.js'; -import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbModalManagerContext} from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; export class UmbTrashEntityAction< T extends UmbItemRepository & { trash(unique: string): Promise }, > extends UmbEntityActionBase { - #modalContext?: UmbModalManagerContext; - - constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { - super(host, repositoryAlias, unique, entityType); - - new UmbContextConsumerController(this._host, UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalContext = instance; - }); - } - async execute() { if (!this.repository) return; @@ -26,18 +13,14 @@ export class UmbTrashEntityAction< if (data) { const item = data[0]; - const modalContext = this.#modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: `Trash ${item.name}`, - content: 'Are you sure you want to move this item to the recycle bin?', - color: 'danger', - confirmLabel: 'Trash', - }, + await umbConfirmModal(this, { + headline: `Trash ${item.name}`, + content: 'Are you sure you want to move this item to the recycle bin?', + color: 'danger', + confirmLabel: 'Trash', }); - modalContext?.onSubmit().then(() => { - this.repository?.trash(this.unique); - }); + this.repository?.trash(this.unique); } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/collection/views/extension-table-action-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/collection/views/extension-table-action-column-layout.element.ts index 1ae41e0fb1..92cdf10ec1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/collection/views/extension-table-action-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/collection/views/extension-table-action-column-layout.element.ts @@ -2,34 +2,20 @@ import { umbExtensionsRegistry } from '../../index.js'; import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api'; import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; @customElement('umb-extension-table-action-column-layout') export class UmbExtensionTableActionColumnLayoutElement extends UmbLitElement { @property({ attribute: false }) value!: ManifestBase; - #modalContext?: UmbModalManagerContext; - - constructor() { - super(); - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalContext = instance; - }); - } - async #removeExtension() { - const modalContext = this.#modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: 'Unload extension', - confirmLabel: 'Unload', - content: html`

      Are you sure you want to unload the extension ${this.value.alias}?

      `, - color: 'danger', - }, + await umbConfirmModal(this, { + headline: 'Unload extension', + confirmLabel: 'Unload', + content: html`

      Are you sure you want to unload the extension ${this.value.alias}?

      `, + color: 'danger', }); - - await modalContext?.onSubmit(); umbExtensionsRegistry.unregister(this.value.alias); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts index 75e162dde2..01ccc82f7f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts @@ -2,13 +2,8 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { type UmbItemRepository, UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; -import type { - UmbModalManagerContext, - UmbModalToken, - UmbPickerModalData, - UmbPickerModalValue, -} from '@umbraco-cms/backoffice/modal'; -import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import type { UmbModalToken, UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from '@umbraco-cms/backoffice/modal'; export class UmbPickerInputContext extends UmbBaseController { // TODO: We are way too unsecure about the requirements for the Modal Token, as we have certain expectation for the data and value. @@ -16,10 +11,6 @@ export class UmbPickerInputContext; #getUnique: (entry: ItemType) => string | undefined; - public modalManager?: UmbModalManagerContext; - - #init: Promise; - #itemManager; selection; @@ -63,13 +54,6 @@ export class UmbPickerInputContext { - this.modalManager = instance; - }).asPromise(), - ]); } getSelection() { @@ -82,10 +66,9 @@ export class UmbPickerInputContext>) { - await this.#init; - if (!this.modalManager) throw new Error('Modal manager context is not initialized'); - - const modalContext = this.modalManager.open(this.modalAlias, { + await this.#itemManager.init; + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const modalContext = modalManager.open(this.modalAlias, { data: { multiple: this._max === 1 ? false : true, ...pickerData, @@ -105,16 +88,12 @@ export class UmbPickerInputContext this.#getUnique(item) === unique); if (!item) throw new Error('Could not find item with unique: ' + unique); - const modalContext = this.modalManager?.open(UMB_CONFIRM_MODAL, { - data: { - color: 'danger', - headline: `Remove ${item.name}?`, - content: 'Are you sure you want to remove this item', - confirmLabel: 'Remove', - }, + await umbConfirmModal(this, { + color: 'danger', + headline: `Remove ${item.name}?`, + content: 'Are you sure you want to remove this item', + confirmLabel: 'Remove', }); - - await modalContext?.onSubmit(); this.#removeItem(unique); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts index 076f360877..45e6e499a2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts @@ -1,38 +1,22 @@ import { UmbEntityActionBase } from '../../../../entity-action/entity-action.js'; -import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbModalManagerContext} from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import type { UmbFolderRepository } from '@umbraco-cms/backoffice/tree'; export class UmbDeleteFolderEntityAction extends UmbEntityActionBase { - #modalContext?: UmbModalManagerContext; - - constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { - super(host, repositoryAlias, unique, entityType); - - new UmbContextConsumerController(this._host, UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalContext = instance; - }); - } - async execute() { - if (!this.repository || !this.#modalContext) return; + if (!this.repository) return; const { data: folder } = await this.repository.request(this.unique); if (folder) { // TODO: maybe we can show something about how many items are part of the folder? - const modalContext = this.#modalContext.open(UMB_CONFIRM_MODAL, { - data: { - headline: `Delete folder ${folder.name}`, - content: 'Are you sure you want to delete this folder?', - color: 'danger', - confirmLabel: 'Delete', - }, - }); - await modalContext.onSubmit(); + await umbConfirmModal(this, { + headline: `Delete folder ${folder.name}`, + content: 'Are you sure you want to delete this folder?', + color: 'danger', + confirmLabel: 'Delete', + }); await this.repository?.delete(this.unique); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/dashboards/redirect-management/dashboard-redirect-management.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/dashboards/redirect-management/dashboard-redirect-management.element.ts index 20e98a216d..55c4451cfd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/dashboards/redirect-management/dashboard-redirect-management.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/dashboards/redirect-management/dashboard-redirect-management.element.ts @@ -1,7 +1,6 @@ import type { UUIButtonState, UUIPaginationElement, UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui'; import { css, html, nothing, customElement, state, query, property } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { RedirectUrlResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import { RedirectManagementResource, RedirectStatusModel } from '@umbraco-cms/backoffice/external/backend-api'; @@ -37,15 +36,6 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement { @query('uui-pagination') private _pagination?: UUIPaginationElement; - private _modalContext?: UmbModalManagerContext; - - constructor() { - super(); - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (_instance) => { - this._modalContext = _instance; - }); - } - connectedCallback() { super.connectedCallback(); this.#getTrackerStatus(); @@ -80,29 +70,23 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement { } // Delete Redirect Action - #onRequestDelete(data: RedirectUrlResponseModel) { + async #onRequestDelete(data: RedirectUrlResponseModel) { if (!data.id) return; - const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: 'Delete', - content: html` -
      -

      ${this.localize.term('redirectUrls_redirectRemoveWarning')}

      - ${this.localize.term('redirectUrls_originalUrl')}: ${data.originalUrl}
      - ${this.localize.term('redirectUrls_redirectedTo')}: ${data.destinationUrl} -
      - `, - color: 'danger', - confirmLabel: 'Delete', - }, + + await umbConfirmModal(this, { + headline: 'Delete', + content: html` +
      +

      ${this.localize.term('redirectUrls_redirectRemoveWarning')}

      + ${this.localize.term('redirectUrls_originalUrl')}: ${data.originalUrl}
      + ${this.localize.term('redirectUrls_redirectedTo')}: ${data.destinationUrl} +
      + `, + color: 'danger', + confirmLabel: 'Delete', }); - modalContext - ?.onSubmit() - .then(() => { - this.#redirectDelete(data.id!); - }) - .catch(() => undefined); + this.#redirectDelete(data.id!); } async #redirectDelete(id: string) { const { error } = await tryExecuteAndNotify(this, RedirectManagementResource.deleteRedirectManagementById({ id })); @@ -125,26 +109,20 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement { } // Tracker disable/enable - #onRequestTrackerToggle() { + async #onRequestTrackerToggle() { if (!this._trackerEnabled) { this.#trackerToggle(); return; } - const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: `${this.localize.term('redirectUrls_disableUrlTracker')}`, - content: `${this.localize.term('redirectUrls_confirmDisable')}`, - color: 'danger', - confirmLabel: 'Disable', - }, + await umbConfirmModal(this, { + headline: `${this.localize.term('redirectUrls_disableUrlTracker')}`, + content: `${this.localize.term('redirectUrls_confirmDisable')}`, + color: 'danger', + confirmLabel: 'Disable', }); - modalContext - ?.onSubmit() - .then(() => { - this.#trackerToggle(); - }) - .catch(() => undefined); + + this.#trackerToggle(); } async #trackerToggle() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-property.element.ts index b9e219b200..6172d07212 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-property.element.ts @@ -2,13 +2,11 @@ import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type'; import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui'; import { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { css, html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal'; import { - UMB_CONFIRM_MODAL, - UMB_MODAL_MANAGER_CONTEXT, UMB_PROPERTY_SETTINGS_MODAL, UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController, + umbConfirmModal, } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { generateAlias } from '@umbraco-cms/backoffice/utils'; @@ -57,7 +55,6 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement { #dataTypeDetailRepository = new UmbDataTypeDetailRepository(this); #modalRegistration; - private _modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; @state() protected _modalRoute?: string; @@ -113,10 +110,6 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement { .observeRouteBuilder((routeBuilder) => { this._editDocumentTypePath = routeBuilder({}); }); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (context) => { - this._modalManagerContext = context; - }); } _partialUpdate(partialObject: UmbPropertyTypeModel) { @@ -137,12 +130,12 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement { this._aliasLocked = !this._aliasLocked; } - #requestRemove(e: Event) { + async #requestRemove(e: Event) { e.preventDefault(); e.stopImmediatePropagation(); if (!this.property || !this.property.id) return; - const modalData: UmbConfirmModalData = { + await umbConfirmModal(this, { headline: `${this.localize.term('actions_delete')} property`, content: html``, confirmLabel: this.localize.term('actions_delete'), color: 'danger', - }; + }); - const modalHandler = this._modalManagerContext?.open(UMB_CONFIRM_MODAL, { data: modalData }); - - modalHandler - ?.onSubmit() - .then(() => { - this.dispatchEvent(new CustomEvent('property-delete')); - }) - .catch(() => { - // We do not need to react to cancel, so we will leave an empty method to prevent Uncaught Promise Rejection error. - return; - }); + this.dispatchEvent(new CustomEvent('property-delete')); } #onNameChange(event: UUIInputEvent) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts index 494e6d5596..be960c0205 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts @@ -15,7 +15,7 @@ import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal'; -import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; @@ -99,8 +99,6 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple private _tabsStructureHelper = new UmbContentTypeContainerStructureHelper(this); - private _modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; - @state() private _compositionConfiguration?: UmbCompositionPickerModalData; @@ -154,10 +152,6 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple this._observeRootGroups(); }); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (context) => { - this._modalManagerContext = context; - }); } private _observeRootGroups() { @@ -220,7 +214,7 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple this._routes = routes; } - #requestRemoveTab(tab: PropertyTypeContainerModelBaseModel | undefined) { + async #requestRemoveTab(tab: PropertyTypeContainerModelBaseModel | undefined) { const modalData: UmbConfirmModalData = { headline: 'Delete tab', content: html` @@ -237,11 +231,9 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple // TODO: If this tab is composed of other tabs, then notify that it will only delete the local tab. - const modalHandler = this._modalManagerContext?.open(UMB_CONFIRM_MODAL, { data: modalData }); + await umbConfirmModal(this, modalData); - modalHandler?.onSubmit().then(() => { - this.#remove(tab?.id); - }); + this.#remove(tab?.id); } #remove(tabId?: string) { if (!tabId) return; @@ -303,7 +295,8 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple } async #openCompositionModal() { - const modalContext = this._modalManagerContext?.open(UMB_COMPOSITION_PICKER_MODAL, { + const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const modalContext = modalManagerContext.open(UMB_COMPOSITION_PICKER_MODAL, { data: this._compositionConfiguration, }); await modalContext?.onSubmit(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/components/log-viewer-search-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/components/log-viewer-search-input.element.ts index 77139d5732..716e47f85c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/components/log-viewer-search-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/components/log-viewer-search-input.element.ts @@ -7,7 +7,7 @@ import type { SavedLogSearchResponseModel } from '@umbraco-cms/backoffice/extern import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { query as getQuery, path, toQueryString } from '@umbraco-cms/backoffice/router'; import type { UmbModalManagerContext, UmbModalContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UmbModalToken, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_MANAGER_CONTEXT, UmbModalToken, umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import './log-viewer-search-input-modal.element.js'; import type { UmbDropdownElement } from '@umbraco-cms/backoffice/components'; @@ -126,20 +126,16 @@ export class UmbLogViewerSearchInputElement extends UmbLitElement { this.#logViewerContext?.saveSearch(savedSearch); } - #removeSearch(name: string) { - const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: this.localize.term('logViewer_deleteSavedSearch'), - content: `${this.localize.term('defaultdialogs_confirmdelete')} ${name}?`, - color: 'danger', - confirmLabel: 'Delete', - }, + async #removeSearch(name: string) { + await umbConfirmModal(this, { + headline: this.localize.term('logViewer_deleteSavedSearch'), + content: `${this.localize.term('defaultdialogs_confirmdelete')} ${name}?`, + color: 'danger', + confirmLabel: 'Delete', }); - modalContext?.onSubmit().then(() => { - this.#logViewerContext?.removeSearch({ name }); - //this.dispatchEvent(new UmbDeleteEvent()); - }); + this.#logViewerContext?.removeSearch({ name }); + //this.dispatchEvent(new UmbDeleteEvent()); } #openSaveSearchDialog() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-property.element.ts index ebd4bfb9ac..5a5f2a0731 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-property.element.ts @@ -4,11 +4,10 @@ import { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { css, html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal'; import { - UMB_CONFIRM_MODAL, - UMB_MODAL_MANAGER_CONTEXT, UMB_PROPERTY_SETTINGS_MODAL, UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController, + umbConfirmModal, } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { generateAlias } from '@umbraco-cms/backoffice/utils'; @@ -57,7 +56,6 @@ export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement { #dataTypeDetailRepository = new UmbDataTypeDetailRepository(this); #modalRegistration; - private _modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; @state() protected _modalRoute?: string; @@ -113,10 +111,6 @@ export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement { .observeRouteBuilder((routeBuilder) => { this._editMediaTypePath = routeBuilder({}); }); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (context) => { - this._modalManagerContext = context; - }); } _partialUpdate(partialObject: UmbPropertyTypeModel) { @@ -137,7 +131,7 @@ export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement { this._aliasLocked = !this._aliasLocked; } - #requestRemove(e: Event) { + async #requestRemove(e: Event) { e.preventDefault(); e.stopImmediatePropagation(); if (!this.property || !this.property.id) return; @@ -154,17 +148,9 @@ export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement { color: 'danger', }; - const modalHandler = this._modalManagerContext?.open(UMB_CONFIRM_MODAL, { data: modalData }); + await umbConfirmModal(this, modalData); - modalHandler - ?.onSubmit() - .then(() => { - this.dispatchEvent(new CustomEvent('property-delete')); - }) - .catch(() => { - // We do not need to react to cancel, so we will leave an empty method to prevent Uncaught Promise Rejection error. - return; - }); + this.dispatchEvent(new CustomEvent('property-delete')); } #onNameChange(event: UUIInputEvent) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts index 1b2d9d7628..3979152bf3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts @@ -11,7 +11,7 @@ import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal'; -import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; @@ -89,8 +89,6 @@ export class UmbMediaTypeWorkspaceViewEditElement extends UmbLitElement implemen private _tabsStructureHelper = new UmbContentTypeContainerStructureHelper(this); - private _modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; - constructor() { super(); this.sorter = new UmbSorterController(this, this.config); @@ -116,10 +114,6 @@ export class UmbMediaTypeWorkspaceViewEditElement extends UmbLitElement implemen ); this._observeRootGroups(); }); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (context) => { - this._modalManagerContext = context; - }); } private _observeRootGroups() { @@ -188,7 +182,7 @@ export class UmbMediaTypeWorkspaceViewEditElement extends UmbLitElement implemen this._routes = routes; } - #requestRemoveTab(tab: PropertyTypeContainerModelBaseModel | undefined) { + async #requestRemoveTab(tab: PropertyTypeContainerModelBaseModel | undefined) { const modalData: UmbConfirmModalData = { headline: 'Delete tab', content: html` @@ -204,12 +198,9 @@ export class UmbMediaTypeWorkspaceViewEditElement extends UmbLitElement implemen }; // TODO: If this tab is composed of other tabs, then notify that it will only delete the local tab. + await umbConfirmModal(this, modalData); - const modalHandler = this._modalManagerContext?.open(UMB_CONFIRM_MODAL, { data: modalData }); - - modalHandler?.onSubmit().then(() => { - this.#remove(tab?.id); - }); + this.#remove(tab?.id); } #remove(tabId?: string) { if (!tabId) return; diff --git a/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/views/created/packages-created-overview.element.ts b/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/views/created/packages-created-overview.element.ts index c279b8bf73..cdf0d64146 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/views/created/packages-created-overview.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/views/created/packages-created-overview.element.ts @@ -4,8 +4,7 @@ import type { PackageDefinitionResponseModel } from '@umbraco-cms/backoffice/ext import { PackageResource } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; @customElement('umb-packages-created-overview') export class UmbPackagesCreatedOverviewElement extends UmbLitElement { @@ -23,19 +22,10 @@ export class UmbPackagesCreatedOverviewElement extends UmbLitElement { @state() private _total?: number; - private _modalContext?: UmbModalManagerContext; - constructor() { super(); - } - connectedCallback(): void { - super.connectedCallback(); this.#getPackages(); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this._modalContext = instance; - }); } async #getPackages() { @@ -106,17 +96,13 @@ export class UmbPackagesCreatedOverviewElement extends UmbLitElement { async #deletePackage(p: PackageDefinitionResponseModel) { if (!p.id) return; - const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - color: 'danger', - headline: `Remove ${p.name}?`, - content: 'Are you sure you want to delete this package', - confirmLabel: 'Delete', - }, + await umbConfirmModal(this, { + color: 'danger', + headline: `Remove ${p.name}?`, + content: 'Are you sure you want to delete this package', + confirmLabel: 'Delete', }); - await modalContext?.onSubmit(); - const { error } = await tryExecuteAndNotify(this, PackageResource.deletePackageCreatedById({ id: p.id })); if (error) return; const index = this._createdPackages.findIndex((x) => x.id === p.id); diff --git a/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/views/installed/installed-packages-section-view-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/views/installed/installed-packages-section-view-item.element.ts index bd0836c6e0..5f363a6fe3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/views/installed/installed-packages-section-view-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/views/installed/installed-packages-section-view-item.element.ts @@ -1,8 +1,7 @@ import { html, css, nothing, ifDefined, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; import { map } from '@umbraco-cms/backoffice/external/rxjs'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api'; import type { ManifestPackageView } from '@umbraco-cms/backoffice/extension-registry'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @@ -43,7 +42,6 @@ export class UmbInstalledPackagesSectionViewItemElement extends UmbLitElement { private _packageView?: ManifestPackageView; #notificationContext?: UmbNotificationContext; - #modalContext?: UmbModalManagerContext; constructor() { super(); @@ -51,9 +49,6 @@ export class UmbInstalledPackagesSectionViewItemElement extends UmbLitElement { this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => { this.#notificationContext = instance; }); - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalContext = instance; - }); } #observePackageView() { @@ -76,16 +71,13 @@ export class UmbInstalledPackagesSectionViewItemElement extends UmbLitElement { async _onMigration() { if (!this.name) return; - const modalContext = this.#modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - color: 'positive', - headline: `Run migrations for ${this.name}?`, - content: `Do you want to start run migrations for ${this.name}`, - confirmLabel: 'Run migrations', - }, - }); - await modalContext?.onSubmit(); + await umbConfirmModal(this, { + color: 'positive', + headline: `Run migrations for ${this.name}?`, + content: `Do you want to start run migrations for ${this.name}`, + confirmLabel: 'Run migrations', + }); this._migrationButtonState = 'waiting'; const { error } = await tryExecuteAndNotify( diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-indexers.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-indexers.ts index 5550281658..0f0a80fa9e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-indexers.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-indexers.ts @@ -1,7 +1,6 @@ import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; import { css, html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import type { IndexResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import { HealthStatusModel, IndexerResource } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -24,16 +23,6 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement { @state() private _loading = true; - private _modalContext?: UmbModalManagerContext; - - constructor() { - super(); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (_instance) => { - this._modalContext = _instance; - }); - } - connectedCallback() { super.connectedCallback(); this._getIndexData(); @@ -55,22 +44,19 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement { } private async _onRebuildHandler() { - const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: `Rebuild ${this.indexName}`, - content: html` - This will cause the index to be rebuilt.
      - Depending on how much content there is in your site this could take a while.
      - It is not recommended to rebuild an index during times of high website traffic or when editors are editing - content. - `, - color: 'danger', - confirmLabel: 'Rebuild', - }, - }); - modalContext?.onSubmit().then(() => { - this._rebuild(); + await umbConfirmModal(this, { + headline: `Rebuild ${this.indexName}`, + content: html` + This will cause the index to be rebuilt.
      + Depending on how much content there is in your site this could take a while.
      + It is not recommended to rebuild an index during times of high website traffic or when editors are editing + content. + `, + color: 'danger', + confirmLabel: 'Rebuild', }); + + this._rebuild(); } private async _rebuild() { this._buttonState = 'waiting'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/published-status/dashboard-published-status.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/published-status/dashboard-published-status.element.ts index 2f3f4e3f54..a5b796a9e8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/published-status/dashboard-published-status.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/published-status/dashboard-published-status.element.ts @@ -1,7 +1,6 @@ import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import { PublishedCacheResource } from '@umbraco-cms/backoffice/external/backend-api'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -24,16 +23,6 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement { @state() private _buttonStateCollect: UUIButtonState = undefined; - private _modalContext?: UmbModalManagerContext; - - constructor() { - super(); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this._modalContext = instance; - }); - } - connectedCallback() { super.connectedCallback(); this._getPublishedStatus(); @@ -68,17 +57,14 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement { } } private async _onReloadCacheHandler() { - const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: 'Reload', - content: html` Trigger a in-memory and local file cache reload on all servers.`, - color: 'danger', - confirmLabel: 'Continue', - }, - }); - modalContext?.onSubmit().then(() => { - this._reloadMemoryCache(); + await umbConfirmModal(this, { + headline: 'Reload', + content: html` Trigger a in-memory and local file cache reload on all servers.`, + color: 'danger', + confirmLabel: 'Continue', }); + + this._reloadMemoryCache(); } // Rebuild @@ -93,17 +79,14 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement { } private async _onRebuildCacheHandler() { - const modalContex = this._modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: 'Rebuild', - content: html` Rebuild content in cmsContentNu database table. Expensive.`, - color: 'danger', - confirmLabel: 'Continue', - }, - }); - modalContex?.onSubmit().then(() => { - this._rebuildDatabaseCache(); + await umbConfirmModal(this, { + headline: 'Rebuild', + content: html` Rebuild content in cmsContentNu database table. Expensive.`, + color: 'danger', + confirmLabel: 'Continue', }); + + this._rebuildDatabaseCache(); } //Collect @@ -118,17 +101,13 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement { } private async _onSnapshotCacheHandler() { - const modalContex = this._modalContext?.open(UMB_CONFIRM_MODAL, { - data: { - headline: 'Snapshot', - content: html` Trigger a NuCache snapshots collection.`, - color: 'danger', - confirmLabel: 'Continue', - }, - }); - modalContex?.onSubmit().then(() => { - this._cacheCollect(); + await umbConfirmModal(this, { + headline: 'Snapshot', + content: html` Trigger a NuCache snapshots collection.`, + color: 'danger', + confirmLabel: 'Continue', }); + this._cacheCollect(); } render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/entity-bulk-actions/delete/delete.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/entity-bulk-actions/delete/delete.action.ts index 4152be03b1..66917857c0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/entity-bulk-actions/delete/delete.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/entity-bulk-actions/delete/delete.action.ts @@ -1,36 +1,19 @@ import type { UmbUserGroupDetailRepository } from '../../repository/index.js'; import { html } from '@umbraco-cms/backoffice/external/lit'; import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; export class UmbDeleteUserGroupEntityBulkAction extends UmbEntityBulkActionBase { - #modalContext?: UmbModalManagerContext; - - constructor(host: UmbControllerHostElement, repositoryAlias: string, selection: Array) { - super(host, repositoryAlias, selection); - - new UmbContextConsumerController(host, UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalContext = instance; - }); - } - async execute() { - if (!this.#modalContext || this.selection.length === 0) return; + if (this.selection.length === 0) return; - const modalContext = this.#modalContext.open(UMB_CONFIRM_MODAL, { - data: { - color: 'danger', - headline: `Delete user groups?`, - content: html`Are you sure you want to delete selected user groups?`, - confirmLabel: 'Delete', - }, + await umbConfirmModal(this, { + color: 'danger', + headline: `Delete user groups?`, + content: html`Are you sure you want to delete selected user groups?`, + confirmLabel: 'Delete', }); - await modalContext.onSubmit(); - //TODO: How should we handle bulk actions? right now we send a request per item we want to change. //TODO: For now we have to reload the page to see the update for (let index = 0; index < this.selection.length; index++) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/disable/disable-user.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/disable/disable-user.action.ts index 389273d25d..1c73b910dc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/disable/disable-user.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/disable/disable-user.action.ts @@ -2,44 +2,32 @@ import type { UmbDisableUserRepository } from '../../repository/disable/disable- import { UmbUserItemRepository } from '../../repository/item/user-item.repository.js'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { - type UmbModalManagerContext, - UMB_MODAL_MANAGER_CONTEXT, - UMB_CONFIRM_MODAL, -} from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; export class UmbDisableUserEntityAction extends UmbEntityActionBase { - #modalManager?: UmbModalManagerContext; #itemRepository: UmbUserItemRepository; constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { super(host, repositoryAlias, unique, entityType); this.#itemRepository = new UmbUserItemRepository(this); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalManager = instance; - }); } async execute() { - if (!this.repository || !this.#modalManager) return; + if (!this.repository) return; const { data } = await this.#itemRepository.requestItems([this.unique]); if (data) { const item = data[0]; - const modalContext = this.#modalManager.open(UMB_CONFIRM_MODAL, { - data: { - headline: `Disable ${item.name}`, - content: 'Are you sure you want to disable this user?', - color: 'danger', - confirmLabel: 'Disable', - }, + await umbConfirmModal(this, { + headline: `Disable ${item.name}`, + content: 'Are you sure you want to disable this user?', + color: 'danger', + confirmLabel: 'Disable', }); - await modalContext.onSubmit(); await this.repository?.disable([this.unique]); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/enable/enable-user.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/enable/enable-user.action.ts index 9b2893ffad..31a08ece23 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/enable/enable-user.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/enable/enable-user.action.ts @@ -2,43 +2,31 @@ import type { UmbEnableUserRepository } from '../../repository/enable/enable-use import { UmbUserItemRepository } from '../../repository/item/user-item.repository.js'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { - type UmbModalManagerContext, - UMB_MODAL_MANAGER_CONTEXT, - UMB_CONFIRM_MODAL, -} from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; export class UmbEnableUserEntityAction extends UmbEntityActionBase { - #modalManager?: UmbModalManagerContext; #itemRepository: UmbUserItemRepository; constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { super(host, repositoryAlias, unique, entityType); this.#itemRepository = new UmbUserItemRepository(this); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalManager = instance; - }); } async execute() { - if (!this.repository || !this.#modalManager) return; + if (!this.repository) return; const { data } = await this.#itemRepository.requestItems([this.unique]); if (data) { const item = data[0]; - const modalContext = this.#modalManager.open(UMB_CONFIRM_MODAL, { - data: { - headline: `Enable ${item.name}`, - content: 'Are you sure you want to enable this user?', - confirmLabel: 'Enable', - }, + await umbConfirmModal(this, { + headline: `Enable ${item.name}`, + content: 'Are you sure you want to enable this user?', + confirmLabel: 'Enable', }); - await modalContext.onSubmit(); await this.repository?.enable([this.unique]); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/unlock/unlock-user.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/unlock/unlock-user.action.ts index f1ad6e0d1b..73163b34c8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/unlock/unlock-user.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/unlock/unlock-user.action.ts @@ -2,43 +2,31 @@ import type { UmbUnlockUserRepository } from '../../repository/index.js'; import { UmbUserItemRepository } from '../../repository/item/user-item.repository.js'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { - type UmbModalManagerContext, - UMB_MODAL_MANAGER_CONTEXT, - UMB_CONFIRM_MODAL, -} from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; export class UmbUnlockUserEntityAction extends UmbEntityActionBase { - #modalManager?: UmbModalManagerContext; #itemRepository: UmbUserItemRepository; constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { super(host, repositoryAlias, unique, entityType); this.#itemRepository = new UmbUserItemRepository(this); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalManager = instance; - }); } async execute() { - if (!this.repository || !this.#modalManager) return; + if (!this.repository) return; const { data } = await this.#itemRepository.requestItems([this.unique]); if (data) { const item = data[0]; - const modalContext = this.#modalManager.open(UMB_CONFIRM_MODAL, { - data: { - headline: `Unlock ${item.name}`, - content: 'Are you sure you want to unlock this user?', - confirmLabel: 'Unlock', - }, + await umbConfirmModal(this, { + headline: `Unlock ${item.name}`, + content: 'Are you sure you want to unlock this user?', + confirmLabel: 'Unlock', }); - await modalContext.onSubmit(); await this.repository?.unlock([this.unique]); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-bulk-actions/delete/delete.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-bulk-actions/delete/delete.action.ts index a01a74021b..92c2713afb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-bulk-actions/delete/delete.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-bulk-actions/delete/delete.action.ts @@ -1,36 +1,19 @@ import type { UmbUserDetailRepository } from '../../repository/index.js'; import { html } from '@umbraco-cms/backoffice/external/lit'; import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; -import type { UmbModalManagerContext} from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; export class UmbUserDeleteEntityBulkAction extends UmbEntityBulkActionBase { - #modalContext?: UmbModalManagerContext; - - constructor(host: UmbControllerHostElement, repositoryAlias: string, selection: Array) { - super(host, repositoryAlias, selection); - - new UmbContextConsumerController(host, UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalContext = instance; - }); - } - async execute() { - if (!this.#modalContext || this.selection.length === 0) return; + if (this.selection.length === 0) return; - const modalContext = this.#modalContext.open(UMB_CONFIRM_MODAL, { - data: { - color: 'danger', - headline: `Delete users?`, - content: html`Are you sure you want to delete selected users?`, - confirmLabel: 'Delete', - }, + await umbConfirmModal(this, { + color: 'danger', + headline: `Delete users?`, + content: html`Are you sure you want to delete selected users?`, + confirmLabel: 'Delete', }); - await modalContext.onSubmit(); - //TODO: How should we handle bulk actions? right now we send a request per item we want to change. //TODO: For now we have to reload the page to see the update for (let index = 0; index < this.selection.length; index++) { From cf1076fecd002c8c0bf82c05522097134b0a696b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:40:01 +0100 Subject: [PATCH 90/98] fix error where selected were appended to frozen array (and not being sent to modal either) --- .../document-variant-picker-modal.element.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts index e6cd88fb31..5d237d3d46 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/variant-picker/document-variant-picker-modal.element.ts @@ -9,6 +9,7 @@ import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { appendToFrozenArray } from '@umbraco-cms/backoffice/observable-api'; @customElement('umb-document-variant-picker-modal') export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement< @@ -33,23 +34,23 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement< } async #setInitialSelection() { - const selected = this.value?.selection ?? []; + let selected = this.value?.selection ?? []; if (selected.length === 0) { // TODO: Make it possible to use consume context without callback. [NL] - const ctrl = this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (appLanguageContext) => {}); + const ctrl = this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, () => {}); const context = await ctrl.asPromise(); const appCulture = context.getAppCulture(); // If the app language is one of the options, select it by default: if (appCulture && this.data?.options.some((o) => o.language.unique === appCulture)) { - selected.push(new UmbVariantId(appCulture, null).toString()); + selected = appendToFrozenArray(selected, new UmbVariantId(appCulture, null).toString()); } ctrl.destroy(); } this.#selectionManager.setMultiple(true); this.#selectionManager.setSelectable(true); - this.#selectionManager.setSelection(this.value?.selection ?? []); + this.#selectionManager.setSelection(selected); if (this.data?.type !== 'unpublish') { this.#selectMandatoryVariants(); From 595a911e775fb1028a00051ce77d5133e6366ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 15:40:50 +0100 Subject: [PATCH 91/98] use _host for entity actions --- .../packages/core/entity-action/common/delete/delete.action.ts | 2 +- .../packages/core/entity-action/common/trash/trash.action.ts | 2 +- .../folder/entity-action/delete-folder/delete-folder.action.ts | 2 +- .../user/user-group/entity-bulk-actions/delete/delete.action.ts | 2 +- .../user/user/entity-actions/disable/disable-user.action.ts | 2 +- .../user/user/entity-actions/enable/enable-user.action.ts | 2 +- .../user/user/entity-actions/unlock/unlock-user.action.ts | 2 +- .../user/user/entity-bulk-actions/delete/delete.action.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.ts index 68f4571c56..95f4d3a407 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.ts @@ -11,7 +11,7 @@ export class UmbDeleteEntityAction< // TOOD: add back when entity actions can support multiple repositories //const { data } = await this.repository.requestItems([this.unique]); - await umbConfirmModal(this, { + await umbConfirmModal(this._host, { headline: `Delete`, content: 'Are you sure you want to delete this item?', color: 'danger', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts index d46f1f5d22..8db299898a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts @@ -13,7 +13,7 @@ export class UmbTrashEntityAction< if (data) { const item = data[0]; - await umbConfirmModal(this, { + await umbConfirmModal(this._host, { headline: `Trash ${item.name}`, content: 'Are you sure you want to move this item to the recycle bin?', color: 'danger', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts index 45e6e499a2..ce2fcfc959 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts @@ -11,7 +11,7 @@ export class UmbDeleteFolderEntityAction extends if (folder) { // TODO: maybe we can show something about how many items are part of the folder? - await umbConfirmModal(this, { + await umbConfirmModal(this._host, { headline: `Delete folder ${folder.name}`, content: 'Are you sure you want to delete this folder?', color: 'danger', diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/entity-bulk-actions/delete/delete.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/entity-bulk-actions/delete/delete.action.ts index 66917857c0..8d26668d56 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/entity-bulk-actions/delete/delete.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/entity-bulk-actions/delete/delete.action.ts @@ -7,7 +7,7 @@ export class UmbDeleteUserGroupEntityBulkAction extends UmbEntityBulkActionBase< async execute() { if (this.selection.length === 0) return; - await umbConfirmModal(this, { + await umbConfirmModal(this._host, { color: 'danger', headline: `Delete user groups?`, content: html`Are you sure you want to delete selected user groups?`, diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/disable/disable-user.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/disable/disable-user.action.ts index 1c73b910dc..a6ac483143 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/disable/disable-user.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/disable/disable-user.action.ts @@ -21,7 +21,7 @@ export class UmbDisableUserEntityAction extends UmbEntityActionBase Date: Tue, 27 Feb 2024 15:53:16 +0100 Subject: [PATCH 92/98] use the built-in repository rather than creating a new one --- .../documents/documents/entity-actions/publish.action.ts | 9 +++++---- .../documents/entity-actions/unpublish.action.ts | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts index f2eb377ec6..622823f6c2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts @@ -1,18 +1,19 @@ import { umbPickDocumentVariantModal } from '../modals/pick-document-variant-modal.controller.js'; -import { UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '../repository/index.js'; +import { type UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '../repository/index.js'; import { UmbDocumentVariantState } from '../types.js'; import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -export class UmbPublishDocumentEntityAction extends UmbEntityActionBase { +export class UmbPublishDocumentEntityAction extends UmbEntityActionBase { async execute() { + if (!this.repository) throw new Error('Document repository not set'); + const languageRepository = new UmbLanguageCollectionRepository(this._host); const { data: languageData } = await languageRepository.requestCollection({}); // TODO: Not sure we need to use the Detail Repository for this, we might do just fine with the tree item model it self. - const documentRepository = new UmbDocumentDetailRepository(this._host); - const { data: documentData } = await documentRepository.requestByUnique(this.unique); + const { data: documentData } = await this.repository.requestByUnique(this.unique); const allOptions = (languageData?.items ?? []).map((language) => ({ language: language, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts index 509b5f1c97..63f237b385 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts @@ -1,18 +1,19 @@ import { umbPickDocumentVariantModal } from '../modals/pick-document-variant-modal.controller.js'; -import { UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '../repository/index.js'; +import { type UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '../repository/index.js'; import { UmbDocumentVariantState } from '../types.js'; import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase { +export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase { async execute() { + if (!this.repository) throw new Error('Document repository not set'); + const languageRepository = new UmbLanguageCollectionRepository(this._host); const { data: languageData } = await languageRepository.requestCollection({}); // TODO: Not sure we need to use the Detail Repository for this, we might do just fine with the tree item model it self. - const documentRepository = new UmbDocumentDetailRepository(this._host); - const { data: documentData } = await documentRepository.requestByUnique(this.unique); + const { data: documentData } = await this.repository.requestByUnique(this.unique); const allOptions = (languageData?.items ?? []).map((language) => ({ language: language, From 0fa54b0864b1e33a6111ad8cfaae88d9b22a0349 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:59:00 +0100 Subject: [PATCH 93/98] if there is only one variant, we can publish directly --- .../documents/documents/entity-actions/publish.action.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts index 622823f6c2..331806481e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts @@ -15,6 +15,14 @@ export class UmbPublishDocumentEntityAction extends UmbEntityActionBase ({ language: language, variant: documentData?.variants.find((variant) => variant.culture === language.unique), From 27e28cad88cb9c0a88b71666b9d560745f297e9f Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:07:13 +0100 Subject: [PATCH 94/98] remove TODO and throw error --- .../documents/documents/entity-actions/publish.action.ts | 8 ++++---- .../documents/entity-actions/unpublish.action.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts index 331806481e..f5b25bef73 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts @@ -11,12 +11,12 @@ export class UmbPublishDocumentEntityAction extends UmbEntityActionBase ({ language: language, - variant: documentData?.variants.find((variant) => variant.culture === language.unique), + variant: documentData.variants.find((variant) => variant.culture === language.unique), unique: new UmbVariantId(language.unique, null).toString(), })); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts index 63f237b385..4eebb4348c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts @@ -11,13 +11,13 @@ export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase ({ language: language, - variant: documentData?.variants.find((variant) => variant.culture === language.unique), + variant: documentData.variants.find((variant) => variant.culture === language.unique), unique: new UmbVariantId(language.unique, null).toString(), })); From 27d6bfa14d0903e8c281f71a97a93f14409ab6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 16:44:46 +0100 Subject: [PATCH 95/98] correct block catalogue data parsing --- .../context/block-grid-entries.context.ts | 43 +++++++++---------- .../block-catalogue-modal.element.ts | 14 +++--- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts index 039d16f8e9..fc2b1924f8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -244,33 +244,32 @@ export class UmbBlockGridEntriesContext // Area entries: if (!this.#areaType) return []; - if (this.#areaType.specifiedAllowance && this.#areaType.specifiedAllowance.length > 0) { - return this.#areaType.specifiedAllowance - .flatMap((permission) => { - if (permission.groupKey) { - return ( - this._manager - ?.getBlockTypes() - .filter( - (blockType) => blockType.groupKey === permission.groupKey && blockType.allowInAreas === true, - ) ?? [] - ); - } else if (permission.elementTypeKey) { - return ( - this._manager?.getBlockTypes().filter((x) => x.contentElementTypeKey === permission.elementTypeKey) ?? - [] - ); - } - return []; - }) - .filter((v, i, a) => a.find((x) => x.contentElementTypeKey === v.contentElementTypeKey) === undefined); + if (this.#areaType.specifiedAllowance && this.#areaType.specifiedAllowance?.length > 0) { + return ( + this.#areaType.specifiedAllowance + .flatMap((permission) => { + if (permission.groupKey) { + return ( + this._manager?.getBlockTypes().filter((blockType) => blockType.groupKey === permission.groupKey) ?? [] + ); + } else if (permission.elementTypeKey) { + return ( + this._manager?.getBlockTypes().filter((x) => x.contentElementTypeKey === permission.elementTypeKey) ?? + [] + ); + } + return []; + }) + // Remove duplicates: + .filter((v, i, a) => a.findIndex((x) => x.contentElementTypeKey === v.contentElementTypeKey) === i) + ); } + // No specific permissions setup, so we will fallback to items allowed in areas: return this._manager.getBlockTypes().filter((x) => x.allowInAreas); } - // If no AreaKey, then we are representing the items of the root: - // Root entries: + // If no AreaKey, then we are in the root, looking for items allowed as root: return this._manager.getBlockTypes().filter((x) => x.allowAtRoot); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index 11832f9772..ae7553f0e1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -16,7 +16,9 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue > { - @state() + // + private _search = ''; + private _groupedBlocks: Array<{ name?: string; blocks: Array }> = []; @state() @@ -28,9 +30,6 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< @state() private _filtered: Array<{ name?: string; blocks: Array }> = []; - @state() - private _search = ''; - constructor() { super(); @@ -68,10 +67,11 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< })); this._groupedBlocks = [{ blocks: noGroupBlocks }, ...grouped]; - this.#onFilter(); + this.#updateFiltered(); } - #onFilter() { + #updateFiltered() { + // A minimum of 3 characters is required to start filtering: if (this._search.length <= 3) { this._filtered = this._groupedBlocks; } else { @@ -84,7 +84,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< #onSearch(e: UUIInputEvent) { this._search = e.target.value as string; - this.#onFilter(); + this.#updateFiltered(); } render() { From 7930a4c98e42a8826a3dd05b8d43a0bf1a827990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 16:51:25 +0100 Subject: [PATCH 96/98] align with current --- .../modals/block-catalogue/block-catalogue-modal.element.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index ae7553f0e1..7765df3f9c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -71,8 +71,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< } #updateFiltered() { - // A minimum of 3 characters is required to start filtering: - if (this._search.length <= 3) { + if (this._search.length === 0) { this._filtered = this._groupedBlocks; } else { const search = this._search.toLowerCase(); @@ -109,7 +108,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< #renderCreateEmpty() { return html` - ${this.data?.blocks && this.data.blocks.length >= 10 + ${this.data?.blocks && this.data.blocks.length > 8 ? html` Date: Tue, 27 Feb 2024 17:28:17 +0100 Subject: [PATCH 97/98] Use method hash for controller alias if not provided --- .../controller-api/controller-alias.type.ts | 2 +- .../libs/controller-api/controller.test.ts | 8 +-- ...e-extension-initializer.controller.test.ts | 2 +- .../extension-api-initializer.test.ts | 4 +- .../extension-element-initializer.test.ts | 4 +- .../observer.controller.test.ts | 58 +++++++++++++++++++ .../observable-api/observer.controller.ts | 4 +- .../src/libs/observable-api/utils/index.ts | 1 + .../utils/simple-hash-code.function.ts | 15 +++++ .../collection-view.manager.test.ts | 4 +- .../selection.manager.test.ts | 2 +- 11 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts create mode 100644 src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/simple-hash-code.function.ts diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts index e25e734e02..6d57214862 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts @@ -1 +1 @@ -export type UmbControllerAlias = string | symbol | undefined; +export type UmbControllerAlias = string | number | symbol | undefined; diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts index e00bf00316..631d169f13 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts @@ -6,9 +6,9 @@ import type { UmbControllerHost } from './controller-host.interface.js'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; @customElement('test-my-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} -export class UmbTestControllerImplementation extends UmbControllerHostMixin(class {}) { +class UmbTestControllerImplementation extends UmbControllerHostMixin(class {}) { testIsConnected = false; testIsDestroyed = false; @@ -47,9 +47,7 @@ export class UmbTestControllerImplementation extends UmbControllerHostMixin(clas } describe('UmbController', () => { - type NewType = UmbControllerHostElement; - - let hostElement: NewType; + let hostElement: UmbControllerHostElement; beforeEach(() => { hostElement = document.createElement('test-my-controller-host') as UmbControllerHostElement; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts index a276cee811..23e639a49f 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts @@ -16,7 +16,7 @@ import { UmbSwitchCondition } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @customElement('umb-test-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} class UmbTestExtensionController extends UmbBaseExtensionInitializer { constructor( diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.test.ts index 469d237d91..96569a144f 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.test.ts @@ -9,9 +9,9 @@ import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; import { type ManifestSection, UmbSwitchCondition } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-test-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} -export class UmbTestApiController extends UmbBaseController { +class UmbTestApiController extends UmbBaseController { public i_am_test_api_controller = true; constructor(host: UmbControllerHost) { diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-initializer.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-initializer.test.ts index a76dffd077..5ba23d9f44 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-initializer.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-initializer.test.ts @@ -1,13 +1,13 @@ import { expect, fixture } from '@open-wc/testing'; import { UmbExtensionRegistry } from '../registry/extension.registry.js'; import { UmbExtensionElementInitializer } from './index.js'; -import type { UmbControllerHostElement} from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; import { type ManifestSection, UmbSwitchCondition } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-test-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} describe('UmbExtensionElementController', () => { describe('Manifest without conditions', () => { diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts new file mode 100644 index 0000000000..0e7dc299b8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts @@ -0,0 +1,58 @@ +import { expect } from '@open-wc/testing'; +import { UmbObjectState } from './states/object-state.js'; +import { UmbObserverController } from './observer.controller.js'; +import { customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; + +@customElement('test-my-observer-controller-host') +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} + +describe('UmbObserverController', () => { + describe('Observer Controllers against other Observer Controllers', () => { + let hostElement: UmbTestControllerHostElement; + + beforeEach(() => { + hostElement = document.createElement('test-my-observer-controller-host') as UmbTestControllerHostElement; + }); + + it('controller is replaced by another controller using the same string as controller-alias', () => { + const state = new UmbObjectState(undefined); + const observable = state.asObservable(); + + const callbackMethod = (state: unknown) => {}; + + const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod, 'my-test-alias'); + const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, 'my-test-alias'); + + expect(hostElement.hasController(firstCtrl)).to.be.false; + expect(hostElement.hasController(secondCtrl)).to.be.true; + }); + + it('controller is replaced by another controller using the the same symbol as controller-alias', () => { + const state = new UmbObjectState(undefined); + const observable = state.asObservable(); + + const callbackMethod = (state: unknown) => {}; + + const mySymbol = Symbol(); + const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod, mySymbol); + const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, mySymbol); + + expect(hostElement.hasController(firstCtrl)).to.be.false; + expect(hostElement.hasController(secondCtrl)).to.be.true; + }); + + it('controller is replacing another controller when using the same callback method and no controller-alias', () => { + const state = new UmbObjectState(undefined); + const observable = state.asObservable(); + + const callbackMethod = (state: unknown) => {}; + + const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod); + const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod); + + expect(hostElement.hasController(firstCtrl)).to.be.false; + expect(hostElement.hasController(secondCtrl)).to.be.true; + }); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts index 5f790c6047..f870d2fe70 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts @@ -1,4 +1,5 @@ import { type ObserverCallback, UmbObserver } from './observer.js'; +import { simpleHashCode } from './utils/simple-hash-code.function.js'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbController, UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -18,7 +19,8 @@ export class UmbObserverController extends UmbObserver implement ) { super(source, callback); this.#host = host; - this.#alias = alias; + // Fallback to use a hash of the provided method: + this.#alias = alias ?? simpleHashCode(callback.toString()); // Lets check if controller is already here: // No we don't want this, as multiple different controllers might be looking at the same source. diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts index c0c329548a..fce5474f94 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts @@ -8,3 +8,4 @@ export * from './naive-object-comparison.function.js'; export * from './observe-multiple.function.js'; export * from './partial-update-frozen-array.function.js'; export * from './push-to-unique-array.function.js'; +export * from './simple-hash-code.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/simple-hash-code.function.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/simple-hash-code.function.ts new file mode 100644 index 0000000000..2e02997326 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/simple-hash-code.function.ts @@ -0,0 +1,15 @@ +/** + * Returns a hash code from a string + * @param {String} str - The string to hash. + * @return {Number} - A 32bit integer + */ +export function simpleHashCode(str: string) { + let hash = 0, + i = 0; + const len = str.length; + while (i < len) { + hash = (hash << 5) - hash + str.charCodeAt(i++); + hash |= 0; // Convert to 32bit integer + } + return hash; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts index 57bed25f32..f3e06f7078 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts @@ -2,12 +2,12 @@ import { expect } from '@open-wc/testing'; import { UmbCollectionViewManager } from './collection-view.manager.js'; import { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; -import type { ManifestCollectionView} from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestCollectionView } from '@umbraco-cms/backoffice/extension-registry'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; @customElement('test-my-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} const VIEW_1_ALIAS = 'UmbTest.CollectionView.1'; const VIEW_2_ALIAS = 'UmbTest.CollectionView.2'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.test.ts index adf7318509..32f70c9825 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.test.ts @@ -5,7 +5,7 @@ import { customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; @customElement('test-my-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} describe('UmbSelectionManager', () => { let manager: UmbSelectionManager; From b8c93be0ca915b914839e44864665eb974067058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 17:33:06 +0100 Subject: [PATCH 98/98] enable to not have any controller alias by passing a null --- .../libs/observable-api/observer.controller.test.ts | 13 +++++++++++++ .../src/libs/observable-api/observer.controller.ts | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts index 0e7dc299b8..076b93d452 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts @@ -54,5 +54,18 @@ describe('UmbObserverController', () => { expect(hostElement.hasController(firstCtrl)).to.be.false; expect(hostElement.hasController(secondCtrl)).to.be.true; }); + + it('controller is NOT replacing another controller when using a null for controller-alias', () => { + const state = new UmbObjectState(undefined); + const observable = state.asObservable(); + + const callbackMethod = (state: unknown) => {}; + + const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod, null); + const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, null); + + expect(hostElement.hasController(firstCtrl)).to.be.true; + expect(hostElement.hasController(secondCtrl)).to.be.true; + }); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts index f870d2fe70..9fa48b5bff 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts @@ -15,12 +15,12 @@ export class UmbObserverController extends UmbObserver implement host: UmbControllerHost, source: Observable, callback: ObserverCallback, - alias?: UmbControllerAlias, + alias?: UmbControllerAlias | null, ) { super(source, callback); this.#host = host; - // Fallback to use a hash of the provided method: - this.#alias = alias ?? simpleHashCode(callback.toString()); + // Fallback to use a hash of the provided method, but only if the alias is undefined. + this.#alias = alias ?? (alias === undefined ? simpleHashCode(callback.toString()) : undefined); // Lets check if controller is already here: // No we don't want this, as multiple different controllers might be looking at the same source.