From dd514189dde7a12704513f12fdeace6cb10d2a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 28 Nov 2023 22:28:11 +0100 Subject: [PATCH 01/21] fix rendering statement --- .../section-main-views/section-main-views.element.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts index c1ceca23f3..1fe1004f6e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts @@ -102,7 +102,8 @@ export class UmbSectionMainViewElement extends UmbLitElement { } #renderDashboards() { - return this._dashboards.length > 0 + // Only show dashboards if there are more than one dashboard or if there are both dashboards and views + return (this._dashboards.length > 0 && this._views.length > 0) || this._dashboards.length > 1 ? html` ${this._dashboards.map((dashboard) => { @@ -121,7 +122,8 @@ export class UmbSectionMainViewElement extends UmbLitElement { } #renderViews() { - return this._views.length > 0 + // Only show views if there are more than one view or if there are both dashboards and views + return (this._views.length > 0 && this._dashboards.length > 0) || this._views.length > 1 ? html` ${this._views.map((view) => { From 5a3653148d8d91e2d5709c86db73e3bf7ee716a7 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 29 Nov 2023 21:07:33 +0100 Subject: [PATCH 02/21] abstract ui culture select logic to its own element --- .../core/localization/components/index.ts | 3 + .../ui-culture-input.element.ts | 109 ++++++++++++++++++ .../src/packages/core/localization/index.ts | 1 + ...user-workspace-profile-settings.element.ts | 66 +---------- 4 files changed, 116 insertions(+), 63 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/localization/components/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/index.ts new file mode 100644 index 0000000000..f95c0295fc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/index.ts @@ -0,0 +1,3 @@ +import './ui-culture-input/ui-culture-input.element.js'; + +export { UmbUiCultureInputElement } from './ui-culture-input/ui-culture-input.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts new file mode 100644 index 0000000000..95f39314a5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts @@ -0,0 +1,109 @@ +import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { css, html, customElement, query, state } from '@umbraco-cms/backoffice/external/lit'; +import { FormControlMixin, UUISelectElement } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; + +interface UmbCultureInputOption { + name: string; + value: string; + selected: boolean; +} + +@customElement('umb-ui-culture-input') +export class UmbUiCultureInputElement extends FormControlMixin(UmbLitElement) { + @state() + private _cultures: Array = []; + + @query('uui-select') + private _selectElement!: HTMLInputElement; + + #currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE; + + constructor() { + super(); + + this.consumeContext(UMB_CURRENT_USER_CONTEXT, (instance) => { + this.#currentUserContext = instance; + this.#observeCurrentUser(); + }); + } + + #observeCurrentUser() { + if (!this.#currentUserContext) return; + + this.observe( + this.#currentUserContext.currentUser, + async (currentUser) => { + if (!currentUser) { + return; + } + + // Find all translations and make a unique list of iso codes + const translations = await firstValueFrom(umbExtensionsRegistry.extensionsOfType('localization')); + + this._cultures = translations + .filter((isoCode) => isoCode !== undefined) + .map((translation) => ({ + value: translation.meta.culture.toLowerCase(), + name: translation.name, + selected: false, + })); + + const currentUserLanguageCode = currentUser.languageIsoCode?.toLowerCase(); + + // Set the current user's language as selected + const currentUserLanguage = this._cultures.find((language) => language.value === currentUserLanguageCode); + + if (currentUserLanguage) { + currentUserLanguage.selected = true; + } else { + // If users language code did not fit any of the options. We will create an option that fits, named unknown. + // In this way the user can keep their choice though a given language was not present at this time. + this._cultures.push({ + value: currentUserLanguageCode ?? 'en-us', + name: currentUserLanguageCode ? `${currentUserLanguageCode} (unknown)` : 'Unknown', + selected: true, + }); + } + }, + 'umbUserObserver', + ); + } + + protected getFormElement() { + return this._selectElement; + } + + #onChange(event: Event) { + event.stopPropagation(); + const target = event.composedPath()[0] as UUISelectElement; + + if (typeof target?.value === 'string') { + this.value = target.value; + this.dispatchEvent(new UmbChangeEvent()); + } + } + + render() { + return html` `; + } + + static styles = [ + css` + :host { + display: block; + } + `, + ]; +} + +export default UmbUiCultureInputElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-ui-culture-input': UmbUiCultureInputElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/index.ts index a87b2663fd..d6ca82fa14 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/index.ts @@ -4,3 +4,4 @@ import './localize-number.element.js'; import './localize-relative-time.element.js'; export * from './registry/localization.registry.js'; +export { UmbUiCultureInputElement } from './components/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts index 75ec003003..6adc4a901b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts @@ -4,32 +4,17 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UUISelectElement } from '@umbraco-cms/backoffice/external/uui'; import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUser } from '@umbraco-cms/backoffice/current-user'; -import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-user-workspace-profile-settings') export class UmbUserWorkspaceProfileSettingsElement extends UmbLitElement { @state() private _user?: UserResponseModel; - @state() - private _currentUser?: UmbCurrentUser; - - @state() - private languages: Array<{ name: string; value: string; selected: boolean }> = []; - - #currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE; #userWorkspaceContext?: typeof UMB_USER_WORKSPACE_CONTEXT.TYPE; constructor() { super(); - this.consumeContext(UMB_CURRENT_USER_CONTEXT, (instance) => { - this.#currentUserContext = instance; - this.#observeCurrentUser(); - }); - this.consumeContext(UMB_USER_WORKSPACE_CONTEXT, (instance) => { this.#userWorkspaceContext = instance; this.observe(this.#userWorkspaceContext.data, (user) => (this._user = user), 'umbUserObserver'); @@ -44,49 +29,6 @@ export class UmbUserWorkspaceProfileSettingsElement extends UmbLitElement { } } - #observeCurrentUser() { - if (!this.#currentUserContext) return; - this.observe( - this.#currentUserContext.currentUser, - async (currentUser) => { - this._currentUser = currentUser; - - if (!currentUser) { - return; - } - - // Find all translations and make a unique list of iso codes - const translations = await firstValueFrom(umbExtensionsRegistry.extensionsOfType('localization')); - - this.languages = translations - .filter((isoCode) => isoCode !== undefined) - .map((translation) => ({ - value: translation.meta.culture.toLowerCase(), - name: translation.name, - selected: false, - })); - - const currentUserLanguageCode = currentUser.languageIsoCode?.toLowerCase(); - - // Set the current user's language as selected - const currentUserLanguage = this.languages.find((language) => language.value === currentUserLanguageCode); - - if (currentUserLanguage) { - currentUserLanguage.selected = true; - } else { - // If users language code did not fit any of the options. We will create an option that fits, named unknown. - // In this way the user can keep their choice though a given language was not present at this time. - this.languages.push({ - value: currentUserLanguageCode ?? 'en-us', - name: currentUserLanguageCode ? `${currentUserLanguageCode} (unknown)` : 'Unknown', - selected: true, - }); - } - }, - 'umbUserObserver', - ); - } - render() { return html`
Profile
@@ -112,13 +54,11 @@ export class UmbUserWorkspaceProfileSettingsElement extends UmbLitElement { - - + label="${this.localize.term('user_language')}"> `; } From 19cad52dc52aadc7c7ee4f8d192a3545df347a23 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 29 Nov 2023 21:11:36 +0100 Subject: [PATCH 03/21] update translation --- src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index 33f798dfed..19de39092d 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -1782,8 +1782,8 @@ export default { inviteAnotherUser: 'Invite another user', inviteUserHelp: 'Invite new users to give them access to Umbraco. An invite email will be sent to the\n user with information on how to log in to Umbraco. Invites last for 72 hours.\n ', - language: 'Language', - languageHelp: 'Set the language you will see in menus and dialogs', + language: 'UI Culture', + languageHelp: 'Set the culture you will see in menus and dialogs', lastLockoutDate: 'Last lockout date', lastLogin: 'Last login', lastPasswordChangeDate: 'Password last changed', From c68cb37f6465f7e03ed2264db376fe20122aa7aa Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 29 Nov 2023 21:38:55 +0100 Subject: [PATCH 04/21] update mock data --- .../src/mocks/data/user/user.data.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts index c10f52fb45..86f8e4332f 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts @@ -25,7 +25,7 @@ export const data: Array = [ mediaStartNodeIds: ['f2f81a40-c989-4b6b-84e2-057cecd3adc1'], name: 'Amelie Walker', email: 'awalker1@domain.com', - languageIsoCode: 'Japanese', + languageIsoCode: 'da-DK', state: UserStateModel.INACTIVE, lastLoginDate: '2023-10-12T18:30:32.879Z', lastLockoutDate: null, @@ -42,7 +42,7 @@ export const data: Array = [ mediaStartNodeIds: [], name: 'Oliver Kim', email: 'okim1@domain.com', - languageIsoCode: 'Russian', + languageIsoCode: 'da-DK', state: UserStateModel.ACTIVE, lastLoginDate: '2023-10-12T18:30:32.879Z', lastLockoutDate: null, @@ -59,7 +59,7 @@ export const data: Array = [ mediaStartNodeIds: [], name: 'Eliana Nieves', email: 'enieves1@domain.com', - languageIsoCode: 'Spanish', + languageIsoCode: 'en-US', state: UserStateModel.INVITED, lastLoginDate: '2023-10-12T18:30:32.879Z', lastLockoutDate: null, @@ -76,7 +76,7 @@ export const data: Array = [ mediaStartNodeIds: [], name: 'Jasmine Patel', email: 'jpatel1@domain.com', - languageIsoCode: 'Hindi', + languageIsoCode: 'en-US', state: UserStateModel.LOCKED_OUT, lastLoginDate: '2023-10-12T18:30:32.879Z', lastLockoutDate: '2023-10-12T18:30:32.879Z', From 39b4b254dc0229f5de00aecaf876aae88c53c58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Thu, 30 Nov 2023 18:32:20 +1300 Subject: [PATCH 05/21] make body layout responsive --- .../core/components/body-layout/body-layout.element.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts index f6dc63079c..00da0fd877 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts @@ -1,4 +1,4 @@ -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, @@ -191,6 +191,7 @@ export class UmbBodyLayoutElement extends LitElement { height: 100%; align-items: center; box-sizing: border-box; + min-width: 0; } #navigation-slot, From b72cd3cd2bbf989071bbf7bc82372d70058eb1c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Thu, 30 Nov 2023 18:33:05 +1300 Subject: [PATCH 06/21] make section main more responsive --- .../packages/core/section/section-main/section-main.element.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts index 642e0f5eac..07daf6531a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts @@ -1,4 +1,4 @@ -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, LitElement, customElement } from '@umbraco-cms/backoffice/external/lit'; @customElement('umb-section-main') @@ -17,6 +17,7 @@ export class UmbSectionMainElement extends LitElement { :host { flex: 1 1 auto; height: 100%; + min-width: 0; } main { From 002afc7a3f01b77723a48db3f52b1dbcc6d86181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Thu, 30 Nov 2023 18:33:23 +1300 Subject: [PATCH 07/21] fix header colors and layout --- .../backoffice-header-sections.element.ts | 48 +++---------------- 1 file changed, 7 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts index 57ef1ba2db..a704b0a227 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts @@ -72,64 +72,30 @@ export class UmbBackofficeHeaderSectionsElement extends UmbLitElement { ?active="${this._currentSectionAlias === section.alias}" href="${`section/${section.manifest?.meta.pathname}`}" label="${section.manifest?.meta.label ?? section.manifest?.name ?? ''}"> - ` + `, )} - ${this._renderExtraSections()}
`; } - private _renderExtraSections() { - return when( - this._extraSections.length > 0, - () => html` - - - - - - - - - - ` - ); - } - render() { return html` ${this._renderSections()} `; } static styles: CSSResultGroup = [ css` + :host { + display: contents; + } #tabs { - color: var(--uui-color-header-contrast); height: 60px; + flex-basis: 100%; font-size: 16px; --uui-tab-text: var(--uui-color-header-contrast); --uui-tab-text-hover: var(--uui-color-header-contrast-emphasis); --uui-tab-text-active: var(--uui-color-header-contrast-emphasis); - --uui-tab-background: var(--uui-color-header-background); - } - - #dropdown { - background-color: white; - border-radius: var(--uui-border-radius); - width: 100%; - height: 100%; - box-sizing: border-box; - box-shadow: var(--uui-shadow-depth-3); - min-width: 200px; - color: black; /* Change to variable */ + background-color: var(--uui-color-header-background); + --uui-tab-group-dropdown-background: var(--uui-color-header-surface); } `, ]; From 3daf6d04c95965dc34598a61e26343c1824ec363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Thu, 30 Nov 2023 19:46:47 +1300 Subject: [PATCH 08/21] cleanup --- .../backoffice-header-sections.element.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts index a704b0a227..3f8762fb65 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts @@ -7,15 +7,9 @@ import { UmbExtensionManifestInitializer } from '@umbraco-cms/backoffice/extensi @customElement('umb-backoffice-header-sections') export class UmbBackofficeHeaderSectionsElement extends UmbLitElement { - @state() - private _open = false; - @state() private _sections: Array> = []; - @state() - private _extraSections: Array = []; - @state() private _currentSectionAlias = ''; @@ -31,18 +25,6 @@ export class UmbBackofficeHeaderSectionsElement extends UmbLitElement { }); } - private _handleMore(e: MouseEvent) { - e.stopPropagation(); - this._open = !this._open; - } - - private _handleLabelClick() { - const moreTab = this.shadowRoot?.getElementById('moreTab'); - moreTab?.setAttribute('active', 'true'); - - this._open = false; - } - private _observeSections() { if (!this._backofficeContext) return; From eaa4a762ab842ae336c7eec562668fc967c4a748 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 10:03:10 +0100 Subject: [PATCH 09/21] pass value to input element + simplify with uui-combobox --- .../ui-culture-input.element.ts | 80 +++++++------------ ...user-workspace-profile-settings.element.ts | 1 + 2 files changed, 31 insertions(+), 50 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts index 95f39314a5..41bdfa2af6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts @@ -1,78 +1,52 @@ -import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { css, html, customElement, query, state } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin, UUISelectElement } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { ManifestLocalization, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; interface UmbCultureInputOption { name: string; value: string; - selected: boolean; } @customElement('umb-ui-culture-input') export class UmbUiCultureInputElement extends FormControlMixin(UmbLitElement) { @state() - private _cultures: Array = []; + private _options: Array = []; - @query('uui-select') + @query('uui-combobox') private _selectElement!: HTMLInputElement; - #currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE; + set value(value: string) { + const oldValue = this._value; + this._value = value.toLowerCase(); + this.requestUpdate('value', oldValue); + } constructor() { super(); - - this.consumeContext(UMB_CURRENT_USER_CONTEXT, (instance) => { - this.#currentUserContext = instance; - this.#observeCurrentUser(); - }); + this.#observeTranslations(); } - #observeCurrentUser() { - if (!this.#currentUserContext) return; - + #observeTranslations() { this.observe( - this.#currentUserContext.currentUser, - async (currentUser) => { - if (!currentUser) { - return; - } - - // Find all translations and make a unique list of iso codes - const translations = await firstValueFrom(umbExtensionsRegistry.extensionsOfType('localization')); - - this._cultures = translations - .filter((isoCode) => isoCode !== undefined) - .map((translation) => ({ - value: translation.meta.culture.toLowerCase(), - name: translation.name, - selected: false, - })); - - const currentUserLanguageCode = currentUser.languageIsoCode?.toLowerCase(); - - // Set the current user's language as selected - const currentUserLanguage = this._cultures.find((language) => language.value === currentUserLanguageCode); - - if (currentUserLanguage) { - currentUserLanguage.selected = true; - } else { - // If users language code did not fit any of the options. We will create an option that fits, named unknown. - // In this way the user can keep their choice though a given language was not present at this time. - this._cultures.push({ - value: currentUserLanguageCode ?? 'en-us', - name: currentUserLanguageCode ? `${currentUserLanguageCode} (unknown)` : 'Unknown', - selected: true, - }); - } + umbExtensionsRegistry.extensionsOfType('localization'), + (localizationManifests) => { + this.#mapToOptions(localizationManifests); }, - 'umbUserObserver', + 'umbObserveLocalizationManifests', ); } + #mapToOptions(manifests: Array) { + this._options = manifests + .filter((isoCode) => isoCode !== undefined) + .map((manifest) => ({ + name: manifest.name, + value: manifest.meta.culture.toLowerCase(), + })); + } + protected getFormElement() { return this._selectElement; } @@ -88,7 +62,13 @@ export class UmbUiCultureInputElement extends FormControlMixin(UmbLitElement) { } render() { - return html` `; + return html` + + ${this._options.map( + (option) => html`${option.name}`, + )} + + `; } static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts index 6adc4a901b..8034a87a80 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts @@ -56,6 +56,7 @@ export class UmbUserWorkspaceProfileSettingsElement extends UmbLitElement { description=${this.localize.term('user_languageHelp')}> From 82ac660b3a916c57df7a9d9c07c2705c3c229ed1 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 11:02:29 +0100 Subject: [PATCH 10/21] lowercase mock data --- .../src/mocks/data/user/user.data.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts index 86f8e4332f..56a7ba6cb7 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts @@ -8,7 +8,7 @@ export const data: Array = [ mediaStartNodeIds: [], name: 'Umbraco User', email: 'noreply@umbraco.com', - languageIsoCode: 'en-US', + languageIsoCode: 'en-us', state: UserStateModel.ACTIVE, lastLoginDate: '9/10/2022', lastLockoutDate: '11/23/2021', @@ -25,7 +25,7 @@ export const data: Array = [ mediaStartNodeIds: ['f2f81a40-c989-4b6b-84e2-057cecd3adc1'], name: 'Amelie Walker', email: 'awalker1@domain.com', - languageIsoCode: 'da-DK', + languageIsoCode: 'da-dk', state: UserStateModel.INACTIVE, lastLoginDate: '2023-10-12T18:30:32.879Z', lastLockoutDate: null, @@ -42,7 +42,7 @@ export const data: Array = [ mediaStartNodeIds: [], name: 'Oliver Kim', email: 'okim1@domain.com', - languageIsoCode: 'da-DK', + languageIsoCode: 'da-dk', state: UserStateModel.ACTIVE, lastLoginDate: '2023-10-12T18:30:32.879Z', lastLockoutDate: null, @@ -59,7 +59,7 @@ export const data: Array = [ mediaStartNodeIds: [], name: 'Eliana Nieves', email: 'enieves1@domain.com', - languageIsoCode: 'en-US', + languageIsoCode: 'en-us', state: UserStateModel.INVITED, lastLoginDate: '2023-10-12T18:30:32.879Z', lastLockoutDate: null, @@ -76,7 +76,7 @@ export const data: Array = [ mediaStartNodeIds: [], name: 'Jasmine Patel', email: 'jpatel1@domain.com', - languageIsoCode: 'en-US', + languageIsoCode: 'en-us', state: UserStateModel.LOCKED_OUT, lastLoginDate: '2023-10-12T18:30:32.879Z', lastLockoutDate: '2023-10-12T18:30:32.879Z', From 0ac9a666a84d5534a23c9247c6bcecd5308c41d1 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 11:02:41 +0100 Subject: [PATCH 11/21] listen for change event --- .../ui-culture-input/ui-culture-input.element.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts index 41bdfa2af6..798fb0344e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts @@ -1,6 +1,6 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { css, html, customElement, query, state } from '@umbraco-cms/backoffice/external/lit'; -import { FormControlMixin, UUISelectElement } from '@umbraco-cms/backoffice/external/uui'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { ManifestLocalization, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @@ -51,9 +51,9 @@ export class UmbUiCultureInputElement extends FormControlMixin(UmbLitElement) { return this._selectElement; } - #onChange(event: Event) { + #onChange(event: UUIComboBoxChangeEvent) { event.stopPropagation(); - const target = event.composedPath()[0] as UUISelectElement; + const target = event.target; if (typeof target?.value === 'string') { this.value = target.value; @@ -62,7 +62,7 @@ export class UmbUiCultureInputElement extends FormControlMixin(UmbLitElement) { } render() { - return html` + return html` ${this._options.map( (option) => html`${option.name}`, From b2768b9361f65bf03c577cae517e594eacaa6ca8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 11:12:43 +0100 Subject: [PATCH 12/21] use correct types --- .../user-workspace-profile-settings.element.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts index 8034a87a80..199332b497 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts @@ -2,7 +2,6 @@ import { UMB_USER_WORKSPACE_CONTEXT } from '../../user-workspace.context.js'; import { html, customElement, state, ifDefined, css } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UUISelectElement } from '@umbraco-cms/backoffice/external/uui'; import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; @customElement('umb-user-workspace-profile-settings') @@ -21,8 +20,8 @@ export class UmbUserWorkspaceProfileSettingsElement extends UmbLitElement { }); } - #onLanguageChange(event: Event) { - const target = event.composedPath()[0] as UUISelectElement; + #onLanguageChange(event: UmbChangeEvent) { + const target = event.target as UmbUiCultureInputElement; if (typeof target?.value === 'string') { this.#userWorkspaceContext?.updateProperty('languageIsoCode', target.value); From 3cfa458a92d03c6d9bd6ffb725606d34eff4f73c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 11:12:54 +0100 Subject: [PATCH 13/21] add value getter --- .../components/ui-culture-input/ui-culture-input.element.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts index 798fb0344e..a93d9ebd1c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts @@ -1,5 +1,5 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -import { css, html, customElement, query, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, query, state, property } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { ManifestLocalization, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @@ -17,6 +17,10 @@ export class UmbUiCultureInputElement extends FormControlMixin(UmbLitElement) { @query('uui-combobox') private _selectElement!: HTMLInputElement; + @property({ type: String }) + get value() { + return this._value; + } set value(value: string) { const oldValue = this._value; this._value = value.toLowerCase(); From 807d6f1531050e49a461d2e6cc93ac16e9e3f403 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 11:52:30 +0100 Subject: [PATCH 14/21] fix type errors --- .../ui-culture-input/ui-culture-input.element.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts index a93d9ebd1c..03d8f5795a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/components/ui-culture-input/ui-culture-input.element.ts @@ -1,6 +1,6 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { css, html, customElement, query, state, property } from '@umbraco-cms/backoffice/external/lit'; -import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { FormControlMixin, UUIComboboxElement, UUIComboboxEvent } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { ManifestLocalization, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @@ -21,10 +21,12 @@ export class UmbUiCultureInputElement extends FormControlMixin(UmbLitElement) { get value() { return this._value; } - set value(value: string) { - const oldValue = this._value; - this._value = value.toLowerCase(); - this.requestUpdate('value', oldValue); + set value(value: FormDataEntryValue | FormData) { + if (typeof value === 'string') { + const oldValue = this._value; + this._value = value.toLowerCase(); + this.requestUpdate('value', oldValue); + } } constructor() { @@ -55,9 +57,9 @@ export class UmbUiCultureInputElement extends FormControlMixin(UmbLitElement) { return this._selectElement; } - #onChange(event: UUIComboBoxChangeEvent) { + #onChange(event: UUIComboboxEvent) { event.stopPropagation(); - const target = event.target; + const target = event.target as UUIComboboxElement; if (typeof target?.value === 'string') { this.value = target.value; From 58fba8eb6b1a809c4e79cf465d139009c1e90a6a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 30 Nov 2023 11:57:31 +0100 Subject: [PATCH 15/21] add missing imports --- .../user-workspace-profile-settings.element.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts index 199332b497..b885012635 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts @@ -3,6 +3,8 @@ import { html, customElement, state, ifDefined, css } from '@umbraco-cms/backoff import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbUiCultureInputElement } from '@umbraco-cms/backoffice/localization'; @customElement('umb-user-workspace-profile-settings') export class UmbUserWorkspaceProfileSettingsElement extends UmbLitElement { From 0c2eca4d92581b6dfd75a8ff69a8ab4caea7d7a2 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:48:03 +0100 Subject: [PATCH 16/21] remove binding registry --- src/Umbraco.Web.UI.Client/.npmrc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/.npmrc b/src/Umbraco.Web.UI.Client/.npmrc index d4f5d5ff86..521a9f7c07 100644 --- a/src/Umbraco.Web.UI.Client/.npmrc +++ b/src/Umbraco.Web.UI.Client/.npmrc @@ -1,2 +1 @@ -@umbraco-cms:registry=https://registry.npmjs.org legacy-peer-deps=true From 2655a555ea1fa76b3ff6b91b4d36d3ed1d040f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 28 Nov 2023 22:03:35 +0100 Subject: [PATCH 17/21] move themes to css --- .../src/{packages/core/themes => css}/themes/dark.theme.css | 0 .../core/themes => css}/themes/high-contrast.theme.css | 0 .../src/packages/core/themes/manifests.ts | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/Umbraco.Web.UI.Client/src/{packages/core/themes => css}/themes/dark.theme.css (100%) rename src/Umbraco.Web.UI.Client/src/{packages/core/themes => css}/themes/high-contrast.theme.css (100%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/themes/themes/dark.theme.css b/src/Umbraco.Web.UI.Client/src/css/themes/dark.theme.css similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/themes/themes/dark.theme.css rename to src/Umbraco.Web.UI.Client/src/css/themes/dark.theme.css diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/themes/themes/high-contrast.theme.css b/src/Umbraco.Web.UI.Client/src/css/themes/high-contrast.theme.css similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/themes/themes/high-contrast.theme.css rename to src/Umbraco.Web.UI.Client/src/css/themes/high-contrast.theme.css diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts index 8754ac2767..44cf6651d5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts @@ -17,14 +17,14 @@ export const themes: Array = [ type: 'theme', alias: 'umb-dark-theme', name: 'Dark', - css: 'src/packages/settings/themes/themes/dark.theme.css', + css: 'src/css/themes/dark.theme.css', weight: 200, }, { type: 'theme', alias: 'umb-high-contrast-theme', name: 'High contrast', - css: 'src/packages/settings/themes/themes/high-contrast.theme.css', + css: 'src/css/themes/high-contrast.theme.css', weight: 100, }, ]; From 8250575422daf8a15c1b54b0ef02fba2cedb7585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 28 Nov 2023 22:04:03 +0100 Subject: [PATCH 18/21] fix loader helpers --- .../functions/load-manifest-api.function.ts | 43 +++++--- .../load-manifest-element.function.ts | 42 +++++--- .../load-manifest-plain-css.function.ts | 33 ++++-- .../load-manifest-plain-js.function.ts | 19 ++-- .../libs/extension-api/types/base.types.ts | 4 +- .../src/libs/extension-api/types/utils.ts | 101 +++++++----------- .../extension-registry/models/theme.model.ts | 2 +- .../src/packages/core/themes/theme.context.ts | 19 +++- 8 files changed, 145 insertions(+), 118 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.ts index ed9db89316..f153cf1e22 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.ts @@ -1,31 +1,44 @@ -import type { UmbApi } from "../models/api.interface.js"; -import type { ApiLoaderExports, ApiLoaderProperty, ClassConstructor, ElementAndApiLoaderProperty, ElementLoaderExports } from "../types/utils.js"; +import type { UmbApi } from '../models/api.interface.js'; +import type { + ApiLoaderExports, + ApiLoaderProperty, + ClassConstructor, + ElementAndApiLoaderProperty, + ElementLoaderExports, +} from '../types/utils.js'; -export async function loadManifestApi(property: ApiLoaderProperty | ElementAndApiLoaderProperty): Promise | undefined> { - const propType = typeof property - if(propType === 'function') { - if((property as ClassConstructor).prototype) { +export async function loadManifestApi( + property: ApiLoaderProperty | ElementAndApiLoaderProperty, +): Promise | undefined> { + const propType = typeof property; + if (propType === 'function') { + if ((property as ClassConstructor).prototype) { // Class Constructor return property as ClassConstructor; } else { // Promise function - const result = await (property as (Exclude, string>, ClassConstructor>))(); - if(typeof result === 'object' && result != null) { - const exportValue = 'api' in result ? result.api : undefined || 'default' in result ? (result as Exclude<(typeof result), ElementLoaderExports>).default : undefined; - if(exportValue && typeof exportValue === 'function') { + const result = await ( + property as Exclude, string>, ClassConstructor> + )(); + if (typeof result === 'object' && result != null) { + const exportValue = + ('api' in result ? result.api : undefined) || + ('default' in result ? (result as Exclude).default : undefined); + if (exportValue && typeof exportValue === 'function') { return exportValue; } } } - } else if(propType === 'string') { + } else if (propType === 'string') { // Import string const result = await (import(/* @vite-ignore */ property as string) as unknown as ApiLoaderExports); - if(typeof result === 'object' && result != null) { - const exportValue = 'api' in result ? result.api : undefined || 'default' in result ? result.default : undefined; - if(exportValue && typeof exportValue === 'function') { + if (result && typeof result === 'object') { + const exportValue = + ('api' in result ? result.api : undefined) || ('default' in result ? result.default : undefined); + if (exportValue && typeof exportValue === 'function') { return exportValue; } } } return undefined; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.ts index b1fcd616d8..89699f7fe0 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.ts @@ -1,28 +1,40 @@ -import type { ApiLoaderExports, ClassConstructor, ElementAndApiLoaderProperty, ElementLoaderExports, ElementLoaderProperty } from "../types/utils.js"; +import type { + ApiLoaderExports, + ClassConstructor, + ElementAndApiLoaderProperty, + ElementLoaderExports, + ElementLoaderProperty, +} from '../types/utils.js'; - -export async function loadManifestElement(property: ElementLoaderProperty | ElementAndApiLoaderProperty): Promise | undefined> { - const propType = typeof property - if(propType === 'function') { - if((property as ClassConstructor).prototype) { +export async function loadManifestElement( + property: ElementLoaderProperty | ElementAndApiLoaderProperty, +): Promise | undefined> { + const propType = typeof property; + if (propType === 'function') { + if ((property as ClassConstructor).prototype) { // Class Constructor return property as ClassConstructor; } else { // Promise function - const result = await (property as (Exclude, ClassConstructor>))(); - if(typeof result === 'object' && result !== null) { - const exportValue = 'element' in result ? result.element : undefined || 'default' in result ? (result as Exclude<(typeof result), ApiLoaderExports>).default : undefined; - if(exportValue && typeof exportValue === 'function') { + const result = await (property as Exclude, ClassConstructor>)(); + if (typeof result === 'object' && result !== null) { + const exportValue = + ('element' in result ? result.element : undefined) || + ('default' in result ? (result as Exclude).default : undefined); + if (exportValue && typeof exportValue === 'function') { return exportValue; } } } - } else if(propType === 'string') { + } else if (propType === 'string') { // Import string - const result = await (import(/* @vite-ignore */ property as string) as unknown as ElementLoaderExports); - if(typeof result === 'object' && result != null) { - const exportValue = 'element' in result ? result.element : undefined || 'default' in result ? result.default : undefined; - if(exportValue && typeof exportValue === 'function') { + const result = await (import( + /* @vite-ignore */ property as string + ) as unknown as ElementLoaderExports); + if (result && typeof result === 'object') { + const exportValue = + ('element' in result ? result.element : undefined) || ('default' in result ? result.default : undefined); + if (exportValue && typeof exportValue === 'function') { return exportValue; } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-css.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-css.function.ts index 01006b2350..861b220ba0 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-css.function.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-css.function.ts @@ -1,18 +1,29 @@ -import type { JsLoaderProperty } from "../types/utils.js"; +import type { CssLoaderExports, CssLoaderProperty } from '../types/utils.js'; -export async function loadManifestPlainCss(property: JsLoaderProperty): Promise { +export async function loadManifestPlainCss( + property: CssLoaderProperty, +): Promise { const propType = typeof property; - if(propType === 'function') { - const result = await (property as (Exclude<(typeof property), string>))(); - if(result != null) { - return result; + if (propType === 'function') { + // Promise function + const result = await (property as Exclude)(); + if (typeof result === 'object' && result != null) { + const exportValue = + ('css' in result ? result.css : undefined) || ('default' in result ? result.default : undefined); + if (exportValue && typeof exportValue === 'string') { + return exportValue as CssType; + } } - } else if(propType === 'string') { + } else if (propType === 'string') { // Import string - const result = await (import(/* @vite-ignore */ property as string) as unknown as CssType); - if(result != null) { - return result; + const result = await (import(/* @vite-ignore */ property as string) as unknown as CssLoaderExports); + if (typeof result === 'object' && result != null) { + const exportValue = + ('css' in result ? result.css : undefined) || ('default' in result ? result.default : undefined); + if (exportValue && typeof exportValue === 'string') { + return exportValue; + } } } return undefined; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.ts index 0611d7eb25..343a5fb952 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.ts @@ -1,18 +1,21 @@ -import type { JsLoaderProperty } from "../types/utils.js"; +import type { JsLoaderProperty } from '../types/utils.js'; -export async function loadManifestPlainJs(property: JsLoaderProperty): Promise { +export async function loadManifestPlainJs( + property: JsLoaderProperty, +): Promise { const propType = typeof property; - if(propType === 'function') { - const result = await (property as (Exclude<(typeof property), string>))(); - if(typeof result === 'object' && result != null) { + if (propType === 'function') { + // Promise function + const result = await (property as Exclude)(); + if (typeof result === 'object' && result != null) { return result; } - } else if(propType === 'string') { + } else if (propType === 'string') { // Import string const result = await (import(/* @vite-ignore */ property as string) as unknown as JsType); - if(typeof result === 'object' && result != null) { + if (typeof result === 'object' && result != null) { return result; } } return undefined; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/base.types.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/base.types.ts index e17bc203fc..017f59aea0 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/base.types.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/base.types.ts @@ -26,12 +26,12 @@ export interface ManifestElementWithElementName extends ManifestElement { elementName: string; } -export interface ManifestPlainCss extends ManifestBase { +export interface ManifestPlainCss extends ManifestBase { /** * The file location of the stylesheet file to load * @TJS-type string */ - css?: CssLoaderProperty; + css?: CssLoaderProperty; } export interface ManifestPlainJs extends ManifestBase { diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts index 9cf7996dd3..bf6ad39b25 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts @@ -1,5 +1,4 @@ -import type { UmbApi } from "../models/index.js"; - +import type { UmbApi } from '../models/index.js'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type HTMLElementConstructor = new (...args: any[]) => T; @@ -7,80 +6,60 @@ export type HTMLElementConstructor = new (...args: any[]) => T; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type ClassConstructor = new (...args: any[]) => T; - - // Module Export Types: -export type ElementLoaderExports< - ElementType extends HTMLElement = HTMLElement -> = ({default: ClassConstructor} | {element: ClassConstructor})// | Omit, 'default'> +export type CssLoaderExports = { default: CssType } | { css: CssType }; -export type ApiLoaderExports< - ApiType extends UmbApi = UmbApi -> = ({default: ClassConstructor} | {api: ClassConstructor})//| Omit, 'default'> +export type ElementLoaderExports = + | { default: ClassConstructor } + | { element: ClassConstructor }; // | Omit, 'default'> + +export type ApiLoaderExports = + | { default: ClassConstructor } + | { api: ClassConstructor }; //| Omit, 'default'> export type ElementAndApiLoaderExports< ElementType extends HTMLElement = HTMLElement, - ApiType extends UmbApi = UmbApi -> = ({api: ClassConstructor} | {element: ClassConstructor} | {api: ClassConstructor, element: ClassConstructor})// | Omit, 'api'>, 'default'> - + ApiType extends UmbApi = UmbApi, +> = + | { api: ClassConstructor } + | { element: ClassConstructor } + | { api: ClassConstructor; element: ClassConstructor }; // | Omit, 'api'>, 'default'> // Promise Types: -export type CssLoaderPromise< - CssType = unknown -> = (() => Promise) +export type CssLoaderPromise = () => Promise>; -export type JsLoaderPromise< - JsType -> = (() => Promise) +export type JsLoaderPromise = () => Promise; -export type ElementLoaderPromise< - ElementType extends HTMLElement = HTMLElement -> = (() => Promise>) +export type ElementLoaderPromise = () => Promise< + ElementLoaderExports +>; -export type ApiLoaderPromise< - ApiType extends UmbApi = UmbApi -> = (() => Promise>) +export type ApiLoaderPromise = () => Promise>; export type ElementAndApiLoaderPromise< ElementType extends HTMLElement = HTMLElement, - ApiType extends UmbApi = UmbApi -> = (() => Promise>) - + ApiType extends UmbApi = UmbApi, +> = () => Promise>; // Property Types: -export type CssLoaderProperty = ( - string - | - CssLoaderPromise -); -export type JsLoaderProperty = ( - string - | - JsLoaderPromise -); -export type ElementLoaderProperty = ( - string - | - ElementLoaderPromise - | - ClassConstructor -); -export type ApiLoaderProperty = ( - string - | - ApiLoaderPromise - | - ClassConstructor -); -export type ElementAndApiLoaderProperty = ( - string - | - ElementAndApiLoaderPromise - | - ElementLoaderPromise - | - ApiLoaderPromise -); \ No newline at end of file +export type CssLoaderProperty = string | CssLoaderPromise; +export type JsLoaderProperty = string | JsLoaderPromise; +export type ElementLoaderProperty = + | string + | ElementLoaderPromise + | ClassConstructor; +export type ApiLoaderProperty = + | string + | ApiLoaderPromise + | ClassConstructor; +export type ElementAndApiLoaderProperty< + ElementType extends HTMLElement = HTMLElement, + ApiType extends UmbApi = UmbApi, +> = + | string + | ElementAndApiLoaderPromise + | ElementLoaderPromise + | ApiLoaderPromise; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/theme.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/theme.model.ts index 37cd90d2a9..2b003bc712 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/theme.model.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/theme.model.ts @@ -2,6 +2,6 @@ import type { ManifestPlainCss } from '@umbraco-cms/backoffice/extension-api'; /** * Theme manifest for styling the backoffice of Umbraco such as dark, high contrast etc */ -export interface ManifestTheme extends ManifestPlainCss { +export interface ManifestTheme extends ManifestPlainCss { type: 'theme'; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/themes/theme.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/themes/theme.context.ts index 333afce863..3bbbb0c81d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/themes/theme.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/themes/theme.context.ts @@ -42,14 +42,16 @@ export class UmbThemeContext extends UmbBaseController { if (themes.length > 0 && themes[0].css) { const activeTheme = themes[0]; if (typeof activeTheme.css === 'function') { - const styleEl = (this.#styleElement = document.createElement('style')); - styleEl.setAttribute('type', 'text/css'); - document.head.appendChild(styleEl); + this.#styleElement = document.createElement('style') as HTMLStyleElement; + // We store the current style element so we can check if it has been replaced by another theme in between. + const currentStyleEl = this.#styleElement; + currentStyleEl.setAttribute('type', 'text/css'); const result = await loadManifestPlainCss(activeTheme.css); // Checking that this is still our styleElement, it has not been replaced with another theme in between. - if (result && styleEl === this.#styleElement) { - styleEl.appendChild(document.createTextNode(result)); + if (result && currentStyleEl === this.#styleElement) { + currentStyleEl.appendChild(document.createTextNode(result)); + document.head.appendChild(currentStyleEl); } } else if (typeof activeTheme.css === 'string') { this.#styleElement = document.createElement('link'); @@ -58,16 +60,23 @@ export class UmbThemeContext extends UmbBaseController { document.head.appendChild(this.#styleElement); } } else { + console.log('remove style element', this.#styleElement); + // We could not load a theme for this alias, so we remove the theme. localStorage.removeItem(LOCAL_STORAGE_KEY); this.#styleElement?.childNodes.forEach((node) => node.remove()); this.#styleElement?.setAttribute('href', ''); + this.#styleElement = null; } }, ); } else { + // Super clean, we got a falsy value, so we remove the theme. + localStorage.removeItem(LOCAL_STORAGE_KEY); + this.#styleElement?.remove(); this.#styleElement?.childNodes.forEach((node) => node.remove()); this.#styleElement?.setAttribute('href', ''); + this.#styleElement = null; } } } From 91f8ce24dd619a593dcfc518e3a23d51e121db29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 1 Dec 2023 10:56:30 +0100 Subject: [PATCH 19/21] move themes --- src/Umbraco.Web.UI.Client/src/css/{themes => }/dark.theme.css | 0 .../src/css/{themes => }/high-contrast.theme.css | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/Umbraco.Web.UI.Client/src/css/{themes => }/dark.theme.css (100%) rename src/Umbraco.Web.UI.Client/src/css/{themes => }/high-contrast.theme.css (100%) diff --git a/src/Umbraco.Web.UI.Client/src/css/themes/dark.theme.css b/src/Umbraco.Web.UI.Client/src/css/dark.theme.css similarity index 100% rename from src/Umbraco.Web.UI.Client/src/css/themes/dark.theme.css rename to src/Umbraco.Web.UI.Client/src/css/dark.theme.css diff --git a/src/Umbraco.Web.UI.Client/src/css/themes/high-contrast.theme.css b/src/Umbraco.Web.UI.Client/src/css/high-contrast.theme.css similarity index 100% rename from src/Umbraco.Web.UI.Client/src/css/themes/high-contrast.theme.css rename to src/Umbraco.Web.UI.Client/src/css/high-contrast.theme.css From d21564482a586e96da14d625d4e5afe4bf934485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 1 Dec 2023 10:56:42 +0100 Subject: [PATCH 20/21] viteStaticCopy --- .../src/packages/core/themes/manifests.ts | 4 ++-- src/Umbraco.Web.UI.Client/vite.config.ts | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts index 44cf6651d5..426b20ba14 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/themes/manifests.ts @@ -17,14 +17,14 @@ export const themes: Array = [ type: 'theme', alias: 'umb-dark-theme', name: 'Dark', - css: 'src/css/themes/dark.theme.css', + css: '/umbraco/backoffice/css/dark.theme.css', weight: 200, }, { type: 'theme', alias: 'umb-high-contrast-theme', name: 'High contrast', - css: 'src/css/themes/high-contrast.theme.css', + css: '/umbraco/backoffice/css/high-contrast.theme.css', weight: 100, }, ]; diff --git a/src/Umbraco.Web.UI.Client/vite.config.ts b/src/Umbraco.Web.UI.Client/vite.config.ts index 93ecae5ddf..35028d98db 100644 --- a/src/Umbraco.Web.UI.Client/vite.config.ts +++ b/src/Umbraco.Web.UI.Client/vite.config.ts @@ -17,6 +17,10 @@ export const plugins: PluginOption[] = [ src: 'public-assets/App_Plugins/custom-bundle-package/*.js', dest: 'App_Plugins/custom-bundle-package', }, + { + src: 'src/css/*.css', + dest: 'umbraco/backoffice/css', + }, { src: 'src/assets/*.svg', dest: 'umbraco/backoffice/assets', @@ -32,7 +36,7 @@ export const plugins: PluginOption[] = [ { src: 'node_modules/msw/lib/iife/**/*', dest: 'umbraco/backoffice/msw', - } + }, ], }), viteTSConfigPaths(), From 7c565dece3d2259656279136741632e214a7d60c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 1 Dec 2023 15:45:07 +0100 Subject: [PATCH 21/21] change prepublishOnly to prepack and merge with build:for:npm --- src/Umbraco.Web.UI.Client/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 2db0728f07..c607ab88d9 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -97,7 +97,6 @@ "backoffice:test:e2e": "npx playwright test", "build-storybook": "npm run wc-analyze && storybook build", "build:for:cms": "npm run build && node ./devops/build/copy-to-cms.js", - "build:for:npm": "npm run build && tsc-alias -f -p src/tsconfig.build.json && npm run generate:jsonschema:dist && npm run wc-analyze && npm run wc-analyze:vscode", "build:for:static": "vite build", "build:vite": "tsc && vite build --mode staging", "build": "tsc --project ./src/tsconfig.build.json && rollup -c ./src/rollup.config.js", @@ -116,7 +115,7 @@ "lint:fix": "npm run lint -- --fix", "lint": "eslint src", "new-extension": "plop --plopfile ./devops/plop/plop.js", - "prepublishOnly": "node ./devops/publish/cleanse-pkg.js", + "prepack": "tsc-alias -f -p src/tsconfig.build.json && npm run generate:jsonschema:dist && npm run wc-analyze && npm run wc-analyze:vscode && node ./devops/publish/cleanse-pkg.js", "preview": "vite preview --open", "storybook:build": "npm run wc-analyze && storybook build", "storybook": "npm run wc-analyze && storybook dev -p 6006",