From a42e53309ca3c1c607bf750a265a7ad99ee082e9 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Mon, 13 Feb 2023 16:39:28 +0100 Subject: [PATCH 001/116] init --- .../src/backoffice/shared/components/index.ts | 6 +- .../input-color-picker.element.ts} | 10 +- .../input-color-picker.test.ts} | 10 +- .../input-eye-dropper.element.ts} | 8 +- .../input-eye-dropper.test.ts} | 10 +- .../input-multi-url-picker.element.ts | 149 ++++++++++++++++++ ...roperty-editor-ui-checkbox-list.element.ts | 1 - ...property-editor-ui-color-picker.element.ts | 6 +- .../property-editor-ui-eye-dropper.element.ts | 5 +- ...erty-editor-ui-multi-url-picker.element.ts | 40 ++++- .../src/core/mocks/data/data-type.data.ts | 7 +- .../modal-layout-multi-url-picker.element.ts | 149 ++++++++++++++++++ .../modal-layout-multi-url-picker.stories.ts | 29 ++++ .../src/core/modal/modal.service.ts | 13 ++ 14 files changed, 408 insertions(+), 35 deletions(-) rename src/Umbraco.Web.UI.Client/src/backoffice/shared/components/{color-picker/color-picker.element.ts => input-color-picker/input-color-picker.element.ts} (81%) rename src/Umbraco.Web.UI.Client/src/backoffice/shared/components/{eye-dropper/eye-dropper.test.ts => input-color-picker/input-color-picker.test.ts} (51%) rename src/Umbraco.Web.UI.Client/src/backoffice/shared/components/{eye-dropper/eye-dropper.element.ts => input-eye-dropper/input-eye-dropper.element.ts} (83%) rename src/Umbraco.Web.UI.Client/src/backoffice/shared/components/{color-picker/color-picker.test.ts => input-eye-dropper/input-eye-dropper.test.ts} (51%) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.stories.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts index 77c784844f..3369fe9172 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts @@ -27,7 +27,9 @@ import '../entity-actions/entity-action-list.element'; import './input-media-picker/input-media-picker.element'; import './input-document-picker/input-document-picker.element'; +import './input-color-picker/input-color-picker.element'; +import './input-eye-dropper/input-eye-dropper.element'; +import './input-checkbox-list/input-checkbox-list.element'; +import './input-multi-url-picker/input-multi-url-picker.element'; import './empty-state/empty-state.element'; - -import './color-picker/color-picker.element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/color-picker/color-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-color-picker/input-color-picker.element.ts similarity index 81% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/components/color-picker/color-picker.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-color-picker/input-color-picker.element.ts index 7532076503..49c62beae4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/color-picker/color-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-color-picker/input-color-picker.element.ts @@ -1,12 +1,12 @@ -import { css, html } from 'lit'; +import { html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property } from 'lit/decorators.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { UUIColorSwatchesEvent } from '@umbraco-ui/uui'; import { UmbLitElement } from '@umbraco-cms/element'; -@customElement('umb-color-picker') -export class UmbColorPickerElement extends FormControlMixin(UmbLitElement) { +@customElement('umb-input-color-picker') +export class UmbInputColorPickerElement extends FormControlMixin(UmbLitElement) { static styles = [UUITextStyles]; @property({ type: Boolean }) @@ -45,10 +45,10 @@ export class UmbColorPickerElement extends FormControlMixin(UmbLitElement) { } } -export default UmbColorPickerElement; +export default UmbInputColorPickerElement; declare global { interface HTMLElementTagNameMap { - 'umb-color-picker': UmbColorPickerElement; + 'umb-input-color-picker': UmbInputColorPickerElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/eye-dropper/eye-dropper.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-color-picker/input-color-picker.test.ts similarity index 51% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/components/eye-dropper/eye-dropper.test.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-color-picker/input-color-picker.test.ts index c147262812..1cc62c08c9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/eye-dropper/eye-dropper.test.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-color-picker/input-color-picker.test.ts @@ -1,15 +1,15 @@ import { expect, fixture, html } from '@open-wc/testing'; -import { UmbEyeDropperElement } from './eye-dropper.element'; +import { UmbInputColorPickerElement } from './input-color-picker.element'; import { defaultA11yConfig } from '@umbraco-cms/test-utils'; -describe('UmbEyeDropperElement', () => { - let element: UmbEyeDropperElement; +describe('UmbInputColorPickerElement', () => { + let element: UmbInputColorPickerElement; beforeEach(async () => { - element = await fixture(html` `); + element = await fixture(html` `); }); it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbEyeDropperElement); + expect(element).to.be.instanceOf(UmbInputColorPickerElement); }); it('passes the a11y audit', async () => { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/eye-dropper/eye-dropper.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-eye-dropper/input-eye-dropper.element.ts similarity index 83% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/components/eye-dropper/eye-dropper.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-eye-dropper/input-eye-dropper.element.ts index 3eb73e9edf..75b173a544 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/eye-dropper/eye-dropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-eye-dropper/input-eye-dropper.element.ts @@ -5,8 +5,8 @@ import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { UUIColorPickerChangeEvent } from '@umbraco-ui/uui'; import { UmbLitElement } from '@umbraco-cms/element'; -@customElement('umb-eye-dropper') -export class UmbEyeDropperElement extends FormControlMixin(UmbLitElement) { +@customElement('umb-input-eye-dropper') +export class UmbInputEyeDropperElement extends FormControlMixin(UmbLitElement) { static styles = [UUITextStyles, css``]; protected getFormElement() { @@ -36,10 +36,10 @@ export class UmbEyeDropperElement extends FormControlMixin(UmbLitElement) { } } -export default UmbEyeDropperElement; +export default UmbInputEyeDropperElement; declare global { interface HTMLElementTagNameMap { - 'umb-eye-dropper': UmbEyeDropperElement; + 'umb-input-eye-dropper': UmbInputEyeDropperElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/color-picker/color-picker.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-eye-dropper/input-eye-dropper.test.ts similarity index 51% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/components/color-picker/color-picker.test.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-eye-dropper/input-eye-dropper.test.ts index 977ceb6a8a..5f471eea16 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/color-picker/color-picker.test.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-eye-dropper/input-eye-dropper.test.ts @@ -1,15 +1,15 @@ import { expect, fixture, html } from '@open-wc/testing'; -import { UmbColorPickerElement } from './color-picker.element'; +import { UmbInputEyeDropperElement } from './input-eye-dropper.element'; import { defaultA11yConfig } from '@umbraco-cms/test-utils'; -describe('UmbColorPickerElement', () => { - let element: UmbColorPickerElement; +describe('UmbInputEyeDropperElement', () => { + let element: UmbInputEyeDropperElement; beforeEach(async () => { - element = await fixture(html` `); + element = await fixture(html` `); }); it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbColorPickerElement); + expect(element).to.be.instanceOf(UmbInputEyeDropperElement); }); it('passes the a11y audit', async () => { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts new file mode 100644 index 0000000000..9235d780b3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts @@ -0,0 +1,149 @@ +import { css, html, nothing } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, property, state } from 'lit/decorators.js'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; +import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { DocumentTreeItemModel, FolderTreeItemModel } from '@umbraco-cms/backend-api'; +import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; + +export type OverlaySize = 'small' | 'medium' | 'large'; + +@customElement('umb-input-multi-url-picker') +export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElement) { + static styles = [ + UUITextStyles, + css` + uui-button { + width: 100%; + } + `, + ]; + + protected getFormElement() { + return undefined; + } + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default undefined + */ + @property({ type: Number }) + min?: number; + + /** + * Min validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + minMessage = 'This field need more items'; + + /** + * This is a maximum amount of selected items in this input. + * @type {number} + * @attr + * @default undefined + */ + @property({ type: Number }) + max?: number; + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + /** + @attr 'hide-anchor' + */ + @property({ type: Boolean, attribute: 'hide-anchor' }) + hideAnchor?: boolean; + + /** + * @type {"small" | "medium" | "large"} + * @attr + * @default "small" + */ + @property() + overlaySize: OverlaySize = 'small'; + + // TODO: do we need both selectedKeys and value? If we just use value we follow the same pattern as native form controls. + private _selectedKeys: Array = []; + public get selectedKeys(): Array { + return this._selectedKeys; + } + public set selectedKeys(keys: Array) { + this._selectedKeys = keys; + super.value = keys.join(','); + } + + @property() + public set value(keysString: string) { + if (keysString !== this._value) { + this.selectedKeys = keysString.split(/[ ,]+/); + } + } + + @state() + private _items?: Array; + + private _modalService?: UmbModalService; + + constructor() { + super(); + + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this._selectedKeys.length < this.min + ); + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this._selectedKeys.length > this.max + ); + + this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { + this._modalService = instance; + }); + } + + private _openPicker() { + const modalHandler = this._modalService?.multiUrlPicker(); + modalHandler?.onClose().then(({ selection }: any) => { + //this._setSelection(selection); + console.log(selection); + }); + } + + render() { + return html`${this._items?.map((item) => this._renderItem(item))} + Add`; + } + + private _renderItem(item: FolderTreeItemModel) { + // TODO: remove when we have a way to handle trashed items + const tempItem = item as FolderTreeItemModel & { isTrashed: boolean }; + + return html` + + ${tempItem.isTrashed ? html` Trashed ` : nothing} + + + `; + } +} + +export default UmbInputMultiUrlPickerElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-multi-url-picker': UmbInputMultiUrlPickerElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/checkbox-list/property-editor-ui-checkbox-list.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/checkbox-list/property-editor-ui-checkbox-list.element.ts index c990aeb92b..b2b7b1cfcb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/checkbox-list/property-editor-ui-checkbox-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/checkbox-list/property-editor-ui-checkbox-list.element.ts @@ -1,7 +1,6 @@ import { html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import '../../../components/input-checkbox-list/input-checkbox-list.element'; import { UmbInputCheckboxListElement } from '../../../components/input-checkbox-list/input-checkbox-list.element'; import { UmbLitElement } from '@umbraco-cms/element'; import type { DataTypePropertyData } from '@umbraco-cms/models'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts index f73d8a6016..108b867422 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts @@ -1,11 +1,9 @@ import { html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { ifDefined } from 'lit/directives/if-defined'; import { UUIColorSwatchesEvent } from '@umbraco-ui/uui'; import { UmbLitElement } from '@umbraco-cms/element'; import type { DataTypePropertyData } from '@umbraco-cms/models'; -import type { UmbColorPickerElement } from 'src/backoffice/shared/components/color-picker/color-picker.element'; /** * @element umb-property-editor-ui-color-picker @@ -38,10 +36,10 @@ export class UmbPropertyEditorUIColorPickerElement extends UmbLitElement { } render() { - return html``; + .showLabels="${this._includeLabels}">`; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/eye-dropper/property-editor-ui-eye-dropper.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/eye-dropper/property-editor-ui-eye-dropper.element.ts index 8ad1e00d87..7fa436884a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/eye-dropper/property-editor-ui-eye-dropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/eye-dropper/property-editor-ui-eye-dropper.element.ts @@ -3,7 +3,6 @@ import { customElement, property, state } from 'lit/decorators.js'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { UUIColorPickerChangeEvent } from '@umbraco-ui/uui'; import { UmbLitElement } from '@umbraco-cms/element'; -import '../../../components/eye-dropper/eye-dropper.element'; import type { DataTypePropertyData } from '@umbraco-cms/models'; /** @@ -37,10 +36,10 @@ export class UmbPropertyEditorUIEyeDropperElement extends UmbLitElement { } render() { - return html``; + .opacity="${this._opacity}">`; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts index dd21df3e24..a6d394ba46 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts @@ -1,7 +1,11 @@ import { html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, property } from 'lit/decorators.js'; +import { customElement, property, state } from 'lit/decorators.js'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; +import { UmbInputMultiUrlPickerElement } from '../../../components/input-multi-url-picker/input-multi-url-picker.element'; +import type { OverlaySize } from '../../../components/input-multi-url-picker/input-multi-url-picker.element'; import { UmbLitElement } from '@umbraco-cms/element'; +import type { DataTypePropertyData } from '@umbraco-cms/models'; /** * @element umb-property-editor-ui-multi-url-picker @@ -10,14 +14,40 @@ import { UmbLitElement } from '@umbraco-cms/element'; export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement { static styles = [UUITextStyles]; - @property() - value = ''; + private _value: string[] = []; + @property({ type: Array }) + public get value(): string[] { + return this._value; + } + public set value(value: string[]) { + this._value = value || []; + } @property({ type: Array, attribute: false }) - public config = []; + public set config(config: DataTypePropertyData[]) { + const overlaySize = config.find((x) => x.alias === 'overlaySize'); + if (overlaySize) this._overlaySize = overlaySize.value as OverlaySize; + + const hideAnchor = config.find((x) => x.alias === 'hideAnchor'); + if (hideAnchor) this._hideAnchor = hideAnchor.value; + } + + @state() + private _overlaySize?: OverlaySize; + @state() + private _hideAnchor?: boolean; + + private _onChange(event: CustomEvent) { + this._value = (event.target as UmbInputMultiUrlPickerElement).selectedKeys; + this.dispatchEvent(new CustomEvent('property-value-change')); + } render() { - return html`
umb-property-editor-ui-multi-url-picker
`; + return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts index 18620770db..45783537fd 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts @@ -324,7 +324,12 @@ export const data: Array = [ isFolder: false, propertyEditorModelAlias: 'Umbraco.MediaPicker3', propertyEditorUIAlias: 'Umb.PropertyEditorUI.MediaPicker', - data: [], + data: [ + { + alias: 'validationLimit', + value: { max: 1 }, + }, + ], }, { name: 'Image Cropper', diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts new file mode 100644 index 0000000000..aafb22eb9e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts @@ -0,0 +1,149 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, state } from 'lit/decorators.js'; +import { UmbModalLayoutElement } from '../modal-layout.element'; + +export interface UmbModalMultiUrlPickerData { + title?: string; + hideAnchor?: boolean; + selection?: string; +} + +import { UmbTreeElement } from '../../../../backoffice/shared/components/tree/tree.element'; + +@customElement('umb-modal-layout-multi-url-picker') +export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement { + static styles = [ + UUITextStyles, + css` + hr { + border: none; + border-bottom: 1px solid var(--uui-color-divider); + margin-bottom: var(--uui-size-space-3); + } + + uui-input, + uui-toggle, + uui-label { + width: 100%; + } + + uui-input, + uui-label { + margin-bottom: var(--uui-size-space-6); + } + + .link-settings { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--uui-size-space-6); + } + + #select-media { + display: block; + } + `, + ]; + + @state() + _selection = ''; + + @state() + _hideAnchor = false; + + @state() + _title = ''; + + connectedCallback() { + super.connectedCallback(); + + this._title = this.data?.title ?? ''; + this._hideAnchor = this.data?.hideAnchor ?? false; + this._selection = this.data?.selection ?? ''; + } + + private _handleSelectionChange(e: CustomEvent) { + e.stopPropagation(); + const element = e.target as UmbTreeElement; + //TODO: Should multiple property be implemented here or be passed down into umb-tree? + this._selection = element.selection[element.selection.length - 1]; + console.log(this._selection); + } + + private _submit() { + this.modalHandler?.close({ selection: this._selection }); + } + + private _close() { + this.modalHandler?.close(); + } + + render() { + return html` + + + + + Link Title + + + Target + Open the link in a new tab + +
+ + ${this._renderTrees()} +
+
+ + +
+
+ `; + } + + private _renderLinkUrlInput() { + if (this._selection) { + return html` + + `; + } else { + return html``; + } + } + + private _renderTrees() { + return html`Link to page + + + +
+ + Link to media + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-modal-layout-multi-url-picker': UmbModalLayoutMultiUrlPickerElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.stories.ts new file mode 100644 index 0000000000..4bb37b0aaf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.stories.ts @@ -0,0 +1,29 @@ +import '../../../../backoffice/shared/components/body-layout/body-layout.element'; +import './modal-layout-multi-url-picker.element'; + +import { Meta, Story } from '@storybook/web-components'; +import { html } from 'lit'; + +import type { + UmbModalLayoutMultiUrlPickerElement, + UmbModalMultiUrlPickerData, +} from './modal-layout-multi-url-picker.element'; + +export default { + title: 'API/Modals/Layouts/Multi Url Picker', + component: 'umb-modal-layout-multi-url-picker', + id: 'modal-layout-multi-url-picker', +} as Meta; + +const data: UmbModalMultiUrlPickerData = { + title: '', + hideAnchor: false, + selection: '', +}; + +export const Overview: Story = () => html` + + +`; diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts b/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts index 0ed747171e..9d3debf9ef 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts @@ -5,6 +5,7 @@ import './layouts/media-picker/modal-layout-media-picker.element'; import './layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element'; import './layouts/modal-layout-current-user.element'; import './layouts/icon-picker/modal-layout-icon-picker.element'; +import './layouts/multi-url-picker/modal-layout-multi-url-picker.element'; import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar'; import { BehaviorSubject } from 'rxjs'; @@ -14,6 +15,7 @@ import type { UmbModalConfirmData } from './layouts/confirm/modal-layout-confirm import type { UmbModalContentPickerData } from './layouts/content-picker/modal-layout-content-picker.element'; import type { UmbModalPropertyEditorUIPickerData } from './layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element'; import type { UmbModalMediaPickerData } from './layouts/media-picker/modal-layout-media-picker.element'; +import type { UmbModalMultiUrlPickerData } from './layouts/multi-url-picker/modal-layout-multi-url-picker.element'; import { UmbModalHandler } from './modal-handler'; import { UmbContextToken } from '@umbraco-cms/context-api'; @@ -86,6 +88,17 @@ export class UmbModalService { return this.open('umb-modal-layout-icon-picker', { data, type: 'sidebar', size: 'small' }); } + /** + * Opens an Multi URL Picker sidebar modal + * @public + * @param {UmbModalMultiUrlPickerData} [data] + * @return {*} {UmbModalHandler} + * @memberof UmbModalService + */ + public multiUrlPicker(data?: UmbModalMultiUrlPickerData): UmbModalHandler { + return this.open('umb-modal-layout-multi-url-picker', { data, type: 'sidebar', size: 'small' }); + } + /** * Opens the user settings sidebar modal * @public From 46d348b443dbb35743468a9ec74a7b26412ba498 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Mon, 13 Feb 2023 16:40:41 +0100 Subject: [PATCH 002/116] dont submit selections on cancel --- .../content-picker/modal-layout-content-picker.element.ts | 2 +- .../layouts/media-picker/modal-layout-media-picker.element.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts index 49d823ebfb..72d991c39d 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/content-picker/modal-layout-content-picker.element.ts @@ -72,7 +72,7 @@ export class UmbModalLayoutContentPickerElement extends UmbModalLayoutElement Date: Mon, 13 Feb 2023 16:38:25 +0000 Subject: [PATCH 003/116] Hacking the planet - trying to add some DEBUG pane to help display what Contexts are available or events are firing --- src/Umbraco.Web.UI.Client/src/app.ts | 4 +- .../shared/components/debug/debug.element.ts | 103 ++++++++++++++++++ .../src/backoffice/shared/components/index.ts | 2 + 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/app.ts b/src/Umbraco.Web.UI.Client/src/app.ts index 22ab81b634..f556e4ae86 100644 --- a/src/Umbraco.Web.UI.Client/src/app.ts +++ b/src/Umbraco.Web.UI.Client/src/app.ts @@ -149,7 +149,9 @@ export class UmbApp extends UmbLitElement { } render() { - return html``; + return html` + + `; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts new file mode 100644 index 0000000000..5af1998733 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -0,0 +1,103 @@ +import { UmbContextRequestEventImplementation, umbContextRequestEventType } from '@umbraco-cms/context-api'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { css, html, LitElement, nothing } from 'lit'; +import { styleMap } from 'lit-html/directives/style-map'; +import { customElement, property, state } from 'lit/decorators.js'; + + +@customElement('umb-debug') +export class UmbDebug extends LitElement { + static styles = [ + UUITextStyles, + css` + :host { + } + + #debug { + display: block; + font-family: monospace; + /* background:red; */ + + position:absolute; + bottom:0; + left:0; + z-index:10000; + width:calc(100% - 20px); + + padding:10px; + } + + #debug:hover { + /* background-color:blue; */ + } + + .events { + background-color:var(--uui-color-danger); + color:var(--uui-color-selected-contrast); + height:0; + transition: height 0.3s ease-out; + } + + .events.open { + height:200px; + padding:10px; + } + + h4 { + margin:0; + } + + `, + ]; + + @property({reflect: true, type: Boolean}) + enabled = false; + + @state() + private _debugPaneOpen = false; + + private _toggleDebugPane() { + this._debugPaneOpen = !this._debugPaneOpen; + } + + constructor() { + super(); + + // Get outer element + const app = window.document.querySelector('umb-app'); + + console.log('root app', app); + app?.addEventListener(umbContextRequestEventType as unknown as UmbContextRequestEventImplementation, (e) => { + // Console.log event + console.log('Some event', e.type); + console.log('Some event thing', e); + console.log('Some event thing', e.contextAlias); + + }); + + //this.addEventListener('click', (e) => console.log(e.type, e.target.localName)); + } + + render() { + + if(this.enabled){ + return html` +
+ Debug +
+

Events

+ +
+
+ `; + } + + return nothing; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-debug': UmbDebug; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts index 77c784844f..31fabab8cb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts @@ -31,3 +31,5 @@ import './input-document-picker/input-document-picker.element'; import './empty-state/empty-state.element'; import './color-picker/color-picker.element'; + +import './debug/debug.element'; \ No newline at end of file From 98f98e2e2891697e197eb80b9d0dc75610f082e6 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 13 Feb 2023 21:37:35 +0000 Subject: [PATCH 004/116] Add bug icon into button If this is going to be placed into any child component/element - need to think how it will work in places woth tiny/limited space in UI --- .../shared/components/debug/debug.element.ts | 76 +++++++++---------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index 5af1998733..4c5a1bb016 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -1,8 +1,7 @@ -import { UmbContextRequestEventImplementation, umbContextRequestEventType } from '@umbraco-cms/context-api'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, LitElement, nothing } from 'lit'; -import { styleMap } from 'lit-html/directives/style-map'; import { customElement, property, state } from 'lit/decorators.js'; +import { UmbContextRequestEventImplementation, umbContextRequestEventType } from '@umbraco-cms/context-api'; @customElement('umb-debug') @@ -10,43 +9,34 @@ export class UmbDebug extends LitElement { static styles = [ UUITextStyles, css` - :host { - } - - #debug { - display: block; + #container { + display: block; font-family: monospace; - /* background:red; */ - position:absolute; - bottom:0; - left:0; - z-index:10000; - width:calc(100% - 20px); + position:absolute; + bottom:0; + left:0; + z-index:10000; - padding:10px; - } + width:calc(100% - 20px); + padding:10px; + } - #debug:hover { - /* background-color:blue; */ - } + .events { + background-color:var(--uui-color-danger); + color:var(--uui-color-selected-contrast); + height:0; + transition: height 0.3s ease-out; + } - .events { - background-color:var(--uui-color-danger); - color:var(--uui-color-selected-contrast); - height:0; - transition: height 0.3s ease-out; - } - - .events.open { - height:200px; - padding:10px; - } - - h4 { - margin:0; - } + .events.open { + height:200px; + padding:10px; + } + h4 { + margin:0; + } `, ]; @@ -66,7 +56,6 @@ export class UmbDebug extends LitElement { // Get outer element const app = window.document.querySelector('umb-app'); - console.log('root app', app); app?.addEventListener(umbContextRequestEventType as unknown as UmbContextRequestEventImplementation, (e) => { // Console.log event console.log('Some event', e.type); @@ -82,16 +71,19 @@ export class UmbDebug extends LitElement { if(this.enabled){ return html` -
- Debug -
-

Events

- -
+
+ + + Debug + + +
+

Events

+
- `; + `; } - + return nothing; } } From c4fa350b047a2bc5d9c5d09c97e07081d341e05d Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 14 Feb 2023 12:08:10 +0000 Subject: [PATCH 005/116] Remove debug element from main umb-app --- src/Umbraco.Web.UI.Client/src/app.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/app.ts b/src/Umbraco.Web.UI.Client/src/app.ts index f556e4ae86..22ab81b634 100644 --- a/src/Umbraco.Web.UI.Client/src/app.ts +++ b/src/Umbraco.Web.UI.Client/src/app.ts @@ -149,9 +149,7 @@ export class UmbApp extends UmbLitElement { } render() { - return html` - - `; + return html``; } } From c00b77791e2d3d636af28dade8cf63b29189214a Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 14 Feb 2023 12:08:30 +0000 Subject: [PATCH 006/116] Rework based on Mads notes --- .../context-api/provide/context-provider.ts | 14 ++++++ .../dashboard-published-status.element.ts | 1 + .../shared/components/debug/debug.element.ts | 46 +++++++++++-------- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts b/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts index ddad843889..3f32cc247b 100644 --- a/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts +++ b/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts @@ -31,6 +31,9 @@ export class UmbContextProvider { public hostConnected() { this.host.addEventListener(umbContextRequestEventType, this._handleContextRequest); this.host.dispatchEvent(new UmbContextProvideEventImplementation(this._contextAlias)); + + // Listen to our debug event 'umb:debug-contexts' + this.host.addEventListener('umb:debug-contexts', this._handleDebugContextRequest); } /** @@ -54,6 +57,17 @@ export class UmbContextProvider { event.callback(this.#instance); }; + private _handleDebugContextRequest = (event: Event) => { + + + console.log('Context Alias:', this._contextAlias); + console.log('Context Instance:', this.#instance); + + // Do I update an array on the event which + // The Debug element can then render in UI?! + console.log('Event:', event); + }; + destroy(): void { // I want to make sure to call this, but for now it was too overwhelming to require the destroy method on context instances. diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts index c91b095df2..58a1db4df7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts @@ -133,6 +133,7 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement { render() { return html` +

${this._publishedStatusText}

div { padding:10px; } @@ -43,6 +43,9 @@ export class UmbDebug extends LitElement { @property({reflect: true, type: Boolean}) enabled = false; + @property({type: Array}) + contextAliases = ['UmbTemplateDetailStore', 'umbLanguageStore']; + @state() private _debugPaneOpen = false; @@ -50,22 +53,18 @@ export class UmbDebug extends LitElement { this._debugPaneOpen = !this._debugPaneOpen; } - constructor() { - super(); + connectedCallback(): void { + super.connectedCallback(); - // Get outer element - const app = window.document.querySelector('umb-app'); + // Create event that bubbles up through the DOM + const event = new CustomEvent('umb:debug-contexts', { + bubbles: true, + composed: true + }); - app?.addEventListener(umbContextRequestEventType as unknown as UmbContextRequestEventImplementation, (e) => { - // Console.log event - console.log('Some event', e.type); - console.log('Some event thing', e); - console.log('Some event thing', e.contextAlias); - - }); - - //this.addEventListener('click', (e) => console.log(e.type, e.target.localName)); - } + // Dispatch it + this.dispatchEvent(event); + } render() { @@ -78,7 +77,14 @@ export class UmbDebug extends LitElement {
-

Events

+
+

Context Aliases to consume

+
    + ${this.contextAliases.map((ctxAlias) => + html`
  • ${ctxAlias}
  • ` + )} +
+
`; From 5c2236fe409c71ef905b2e6757e0637c57d67a94 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 14 Feb 2023 14:12:51 +0000 Subject: [PATCH 007/116] Some work witrh help of pairing with Mads to get the contexts collected and then passed to callback on original firing event --- .../consume/context-request.event.ts | 8 +++ .../context-api/provide/context-provider.ts | 24 ++++---- src/Umbraco.Web.UI.Client/src/app.ts | 11 ++++ .../shared/components/debug/debug.element.ts | 57 ++++++++++++++----- 4 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/libs/context-api/consume/context-request.event.ts b/src/Umbraco.Web.UI.Client/libs/context-api/consume/context-request.event.ts index 9d8fa86dec..ab96c5bd51 100644 --- a/src/Umbraco.Web.UI.Client/libs/context-api/consume/context-request.event.ts +++ b/src/Umbraco.Web.UI.Client/libs/context-api/consume/context-request.event.ts @@ -1,6 +1,7 @@ import { UmbContextToken } from '../context-token'; export const umbContextRequestEventType = 'umb:context-request'; +export const umbDebugContextEventType = 'umb:debug-contexts'; export type UmbContextCallback = (instance: T) => void; @@ -31,3 +32,10 @@ export class UmbContextRequestEventImplementation extends Event imp export const isUmbContextRequestEvent = (event: Event): event is UmbContextRequestEventImplementation => { return event.type === umbContextRequestEventType; }; + + +export class UmbContextDebugRequest extends Event { + public constructor(public readonly callback:any) { + super(umbDebugContextEventType, { bubbles: true, composed: true, cancelable: false }); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts b/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts index 3f32cc247b..afeb0b50cb 100644 --- a/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts +++ b/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts @@ -1,4 +1,5 @@ -import { umbContextRequestEventType, isUmbContextRequestEvent } from '../consume/context-request.event'; +import { map } from 'rxjs'; +import { umbContextRequestEventType, isUmbContextRequestEvent, umbDebugContextEventType } from '../consume/context-request.event'; import { UmbContextToken } from '../context-token'; import { UmbContextProvideEventImplementation } from './context-provide.event'; @@ -33,7 +34,7 @@ export class UmbContextProvider { this.host.dispatchEvent(new UmbContextProvideEventImplementation(this._contextAlias)); // Listen to our debug event 'umb:debug-contexts' - this.host.addEventListener('umb:debug-contexts', this._handleDebugContextRequest); + this.host.addEventListener(umbDebugContextEventType, this._handleDebugContextRequest); } /** @@ -57,15 +58,18 @@ export class UmbContextProvider { event.callback(this.#instance); }; - private _handleDebugContextRequest = (event: Event) => { + private _handleDebugContextRequest = (event: any) => { + // If the event doesn't have an instances property, create it. + if(!event.instances){ + event.instances = new Map(); + } - - console.log('Context Alias:', this._contextAlias); - console.log('Context Instance:', this.#instance); - - // Do I update an array on the event which - // The Debug element can then render in UI?! - console.log('Event:', event); + // If the event doesn't have an instance for this context, add it. + // Nearest to the DOM element of will be added first + // as contexts can change/override deeper in the DOM + if(!event.instances.has(this._contextAlias)){ + event.instances.set(this._contextAlias, this.#instance); + } }; diff --git a/src/Umbraco.Web.UI.Client/src/app.ts b/src/Umbraco.Web.UI.Client/src/app.ts index 22ab81b634..18d11ccbf2 100644 --- a/src/Umbraco.Web.UI.Client/src/app.ts +++ b/src/Umbraco.Web.UI.Client/src/app.ts @@ -19,6 +19,7 @@ import { UmbLitElement } from '@umbraco-cms/element'; import { tryExecuteAndNotify } from '@umbraco-cms/resources'; import { OpenAPI, RuntimeLevelModel, ServerResource } from '@umbraco-cms/backend-api'; import { UmbIconStore } from '@umbraco-cms/store'; +import { UmbContextDebugRequest, umbDebugContextEventType } from '@umbraco-cms/context-api'; @customElement('umb-app') export class UmbApp extends UmbLitElement { @@ -83,6 +84,16 @@ export class UmbApp extends UmbLitElement { await this._setInitStatus(); await this._registerExtensionManifestsFromServer(); this._redirect(); + + // Listen for the debug event from the component + this.addEventListener(umbDebugContextEventType, (event: any) => { + // Once we got to the outter most component + // we can send the event containing all the contexts + // we have collected whilst coming up through the DOM + // and pass it back down to the callback in + // the component that originally fired the event + event.callback(event.instances); + }); } private async _setup() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index c3792acc7d..9afe71faab 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -1,7 +1,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, LitElement, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { UmbContextRequestEventImplementation, umbContextRequestEventType } from '@umbraco-cms/context-api'; +import { UmbContextDebugRequest, UmbContextRequestEventImplementation, umbContextRequestEventType } from '@umbraco-cms/context-api'; @customElement('umb-debug') @@ -27,7 +27,7 @@ export class UmbDebug extends LitElement { } .events.open { - height:200px; + height:auto; } .events > div { @@ -43,8 +43,8 @@ export class UmbDebug extends LitElement { @property({reflect: true, type: Boolean}) enabled = false; - @property({type: Array}) - contextAliases = ['UmbTemplateDetailStore', 'umbLanguageStore']; + @property() + contextAliases = new Map(); @state() private _debugPaneOpen = false; @@ -55,15 +55,13 @@ export class UmbDebug extends LitElement { connectedCallback(): void { super.connectedCallback(); - - // Create event that bubbles up through the DOM - const event = new CustomEvent('umb:debug-contexts', { - bubbles: true, - composed: true - }); - + // Dispatch it - this.dispatchEvent(event); + this.dispatchEvent(new UmbContextDebugRequest((instances)=> { + console.log('I have contexts now', instances); + + this.contextAliases = instances; + })); } render() { @@ -80,9 +78,7 @@ export class UmbDebug extends LitElement {

Context Aliases to consume

    - ${this.contextAliases.map((ctxAlias) => - html`
  • ${ctxAlias}
  • ` - )} + ${this._renderContextAliases()}
@@ -92,6 +88,37 @@ export class UmbDebug extends LitElement { return nothing; } + + private _renderContextAliases() { + const aliases = []; + + for (const [alias, instance] of this.contextAliases) { + aliases.push( + html` +
  • + ${alias} +
      + ${this._renderInstance(instance)} +
    +
  • ` + ); + } + + return aliases; + } + + private _renderInstance(instance: any) { + const instanceKeys = []; + + for(const key in instance) { + // Goes KABOOM - if try to loop over the class/object + // instanceKeys.push(html`
  • ${key} = ${instance[key]}
  • `); + + instanceKeys.push(html`
  • ${key}
  • `); + } + + return instanceKeys; + } } declare global { From 0430b3b2e2174a0a15257a6d9c7e58f55c377f72 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 14 Feb 2023 14:59:29 +0000 Subject: [PATCH 008/116] Try and display simple string values --- .../shared/components/debug/debug.element.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index 9afe71faab..eeddd4ec30 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -96,7 +96,7 @@ export class UmbDebug extends LitElement { aliases.push( html`
  • - ${alias} + Context: ${alias}
      ${this._renderInstance(instance)}
    @@ -114,7 +114,15 @@ export class UmbDebug extends LitElement { // Goes KABOOM - if try to loop over the class/object // instanceKeys.push(html`
  • ${key} = ${instance[key]}
  • `); - instanceKeys.push(html`
  • ${key}
  • `); + // console.log(`key: ${key} = ${value} TYPEOF: ${typeof value}`); + + const value = instance[key]; + if(typeof value === 'string'){ + instanceKeys.push(html`
  • ${key} = ${instance[key]}
  • `); + } + else { + instanceKeys.push(html`
  • ${key}
  • `); + } } return instanceKeys; From e311465a1e0d2d28ce0b98b8f1903fcba49d0bce Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 15 Feb 2023 09:06:31 +0000 Subject: [PATCH 009/116] Remove unused import --- .../libs/context-api/provide/context-provider.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts b/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts index f9b1bfd189..c9ce3ab9c3 100644 --- a/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts +++ b/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts @@ -1,4 +1,3 @@ -import { map } from 'rxjs'; import { umbContextRequestEventType, isUmbContextRequestEvent, umbDebugContextEventType } from '../consume/context-request.event'; import { UmbContextToken } from '../context-token'; import { UmbContextProvideEventImplementation } from './context-provide.event'; @@ -72,6 +71,7 @@ export class UmbContextProvider { if(!event.instances){ event.instances = new Map(); } + // If the event doesn't have an instance for this context, add it. // Nearest to the DOM element of will be added first // as contexts can change/override deeper in the DOM @@ -80,7 +80,6 @@ export class UmbContextProvider { } }; - destroy(): void { // I want to make sure to call this, but for now it was too overwhelming to require the destroy method on context instances. (this.#instance as any).destroy?.(); From 6da0a016f2dfea3c2af4576a3d34fa4979e4ff05 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 15 Feb 2023 10:22:32 +0100 Subject: [PATCH 010/116] add method to get method names of instance --- .../shared/components/debug/debug.element.ts | 118 +++++++++++------- 1 file changed, 73 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index eeddd4ec30..5bb8792dfc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -1,8 +1,11 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, LitElement, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { UmbContextDebugRequest, UmbContextRequestEventImplementation, umbContextRequestEventType } from '@umbraco-cms/context-api'; - +import { + UmbContextDebugRequest, + UmbContextRequestEventImplementation, + umbContextRequestEventType, +} from '@umbraco-cms/context-api'; @customElement('umb-debug') export class UmbDebug extends LitElement { @@ -13,63 +16,64 @@ export class UmbDebug extends LitElement { display: block; font-family: monospace; - z-index:10000; + z-index: 10000; - width:100%; - padding:10px 0; + width: 100%; + padding: 10px 0; } .events { - background-color:var(--uui-color-danger); - color:var(--uui-color-selected-contrast); - height:0; + background-color: var(--uui-color-danger); + color: var(--uui-color-selected-contrast); + height: 0; transition: height 0.3s ease-out; } .events.open { - height:auto; + height: auto; } .events > div { - padding:10px; + padding: 10px; } h4 { - margin:0; + margin: 0; } `, ]; - @property({reflect: true, type: Boolean}) - enabled = false; + @property({ reflect: true, type: Boolean }) + enabled = false; @property() contextAliases = new Map(); - @state() + @state() private _debugPaneOpen = false; - private _toggleDebugPane() { - this._debugPaneOpen = !this._debugPaneOpen; - } + private _toggleDebugPane() { + this._debugPaneOpen = !this._debugPaneOpen; + } connectedCallback(): void { super.connectedCallback(); - - // Dispatch it - this.dispatchEvent(new UmbContextDebugRequest((instances)=> { - console.log('I have contexts now', instances); - this.contextAliases = instances; - })); + // Dispatch it + this.dispatchEvent( + new UmbContextDebugRequest((instances) => { + console.log('I have contexts now', instances); + + this.contextAliases = instances; + }) + ); } render() { - - if(this.enabled){ - return html` -
    - + if (this.enabled) { + return html` +
    + Debug @@ -80,13 +84,13 @@ export class UmbDebug extends LitElement {
      ${this._renderContextAliases()}
    -
    +
    - + `; - } + } - return nothing; + return nothing; } private _renderContextAliases() { @@ -94,14 +98,13 @@ export class UmbDebug extends LitElement { for (const [alias, instance] of this.contextAliases) { aliases.push( - html` -
  • - Context: ${alias} -
      - ${this._renderInstance(instance)} -
    -
  • ` - ); + html`
  • + Context: ${alias} +
      + ${this._renderInstance(instance)} +
    +
  • ` + ); } return aliases; @@ -110,23 +113,48 @@ export class UmbDebug extends LitElement { private _renderInstance(instance: any) { const instanceKeys = []; - for(const key in instance) { + if (typeof instance === 'object') { + const methodNames = this.getClassMethodNames(instance); + instanceKeys.push(html`
  • Methods - ${methodNames.join(', ')}
  • `); + // instanceKeys.push(html`
  • Method -
  • `); + } + + for (const key in instance) { + if (key.startsWith('_')) { + continue; + } // Goes KABOOM - if try to loop over the class/object // instanceKeys.push(html`
  • ${key} = ${instance[key]}
  • `); // console.log(`key: ${key} = ${value} TYPEOF: ${typeof value}`); const value = instance[key]; - if(typeof value === 'string'){ - instanceKeys.push(html`
  • ${key} = ${instance[key]}
  • `); - } - else { + if (typeof value === 'string') { + instanceKeys.push(html`
  • ${key} = ${value}
  • `); + } else { instanceKeys.push(html`
  • ${key}
  • `); } } return instanceKeys; } + + private getClassMethodNames(klass: any) { + const isGetter = (x: any, name: string): boolean => !!(Object.getOwnPropertyDescriptor(x, name) || {}).get; + const isFunction = (x: any, name: string): boolean => typeof x[name] === 'function'; + const deepFunctions = (x: any): any => + x !== Object.prototype && + Object.getOwnPropertyNames(x) + .filter((name) => isGetter(x, name) || isFunction(x, name)) + .concat(deepFunctions(Object.getPrototypeOf(x)) || []); + const distinctDeepFunctions = (klass: any) => Array.from(new Set(deepFunctions(klass))); + + const allMethods = + typeof klass.prototype === 'undefined' + ? distinctDeepFunctions(klass) + : Object.getOwnPropertyNames(klass.prototype); + return allMethods.filter((name: any) => name !== 'constructor' && !name.startsWith('_')); + } } declare global { From 00662c914494db8380d9661160afa55cb62aed24 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 15 Feb 2023 10:32:34 +0100 Subject: [PATCH 011/116] add type information --- .../shared/components/debug/debug.element.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index 5bb8792dfc..10e2f55e8c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -1,11 +1,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, LitElement, nothing, TemplateResult } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { - UmbContextDebugRequest, - UmbContextRequestEventImplementation, - umbContextRequestEventType, -} from '@umbraco-cms/context-api'; +import { UmbContextDebugRequest } from '@umbraco-cms/context-api'; @customElement('umb-debug') export class UmbDebug extends LitElement { @@ -61,7 +57,7 @@ export class UmbDebug extends LitElement { // Dispatch it this.dispatchEvent( - new UmbContextDebugRequest((instances) => { + new UmbContextDebugRequest((instances: Map) => { console.log('I have contexts now', instances); this.contextAliases = instances; @@ -100,6 +96,7 @@ export class UmbDebug extends LitElement { aliases.push( html`
  • Context: ${alias} + (${instance.toString()})
      ${this._renderInstance(instance)}
    @@ -111,12 +108,15 @@ export class UmbDebug extends LitElement { } private _renderInstance(instance: any) { - const instanceKeys = []; + const instanceKeys: TemplateResult[] = []; - if (typeof instance === 'object') { - const methodNames = this.getClassMethodNames(instance); + if (typeof instance !== 'object') { + return instanceKeys; + } + + const methodNames = this.getClassMethodNames(instance); + if (methodNames.length) { instanceKeys.push(html`
  • Methods - ${methodNames.join(', ')}
  • `); - // instanceKeys.push(html`
  • Method -
  • `); } for (const key in instance) { @@ -132,7 +132,7 @@ export class UmbDebug extends LitElement { if (typeof value === 'string') { instanceKeys.push(html`
  • ${key} = ${value}
  • `); } else { - instanceKeys.push(html`
  • ${key}
  • `); + instanceKeys.push(html`
  • ${key} (${typeof value})
  • `); } } From c1ae1aae6b778c40664e72ee55f1db92a8aa067b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 15 Feb 2023 10:34:30 +0100 Subject: [PATCH 012/116] use typeof instead as it works with simple types --- .../src/backoffice/shared/components/debug/debug.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index 10e2f55e8c..ad695d592a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -96,7 +96,7 @@ export class UmbDebug extends LitElement { aliases.push( html`
  • Context: ${alias} - (${instance.toString()}) + (${typeof instance})
      ${this._renderInstance(instance)}
    From aee33197df6f369f422c4bb8f81a01e88785f995 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 15 Feb 2023 10:39:39 +0100 Subject: [PATCH 013/116] add support for functions and primitives --- .../shared/components/debug/debug.element.ts | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index ad695d592a..3648a424bb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -110,30 +110,32 @@ export class UmbDebug extends LitElement { private _renderInstance(instance: any) { const instanceKeys: TemplateResult[] = []; - if (typeof instance !== 'object') { - return instanceKeys; - } - - const methodNames = this.getClassMethodNames(instance); - if (methodNames.length) { - instanceKeys.push(html`
  • Methods - ${methodNames.join(', ')}
  • `); - } - - for (const key in instance) { - if (key.startsWith('_')) { - continue; + if (typeof instance === 'function') { + return instanceKeys.push(html`
  • Callable Function
  • `); + } else if (typeof instance === 'object') { + const methodNames = this.getClassMethodNames(instance); + if (methodNames.length) { + instanceKeys.push(html`
  • Methods - ${methodNames.join(', ')}
  • `); } - // Goes KABOOM - if try to loop over the class/object - // instanceKeys.push(html`
  • ${key} = ${instance[key]}
  • `); - // console.log(`key: ${key} = ${value} TYPEOF: ${typeof value}`); + for (const key in instance) { + if (key.startsWith('_')) { + continue; + } + // Goes KABOOM - if try to loop over the class/object + // instanceKeys.push(html`
  • ${key} = ${instance[key]}
  • `); - const value = instance[key]; - if (typeof value === 'string') { - instanceKeys.push(html`
  • ${key} = ${value}
  • `); - } else { - instanceKeys.push(html`
  • ${key} (${typeof value})
  • `); + // console.log(`key: ${key} = ${value} TYPEOF: ${typeof value}`); + + const value = instance[key]; + if (typeof value === 'string') { + instanceKeys.push(html`
  • ${key} = ${value}
  • `); + } else { + instanceKeys.push(html`
  • ${key} (${typeof value})
  • `); + } } + } else { + instanceKeys.push(html`
  • Context is a primitive with value: ${instance}
  • `); } return instanceKeys; From 1be4da9f3b802cf4b8f5050c5ee58b4d288e8cd6 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 15 Feb 2023 10:56:32 +0100 Subject: [PATCH 014/116] fix height transition + add scroll --- .../backoffice/shared/components/debug/debug.element.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index 3648a424bb..9208c35073 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -21,12 +21,14 @@ export class UmbDebug extends LitElement { .events { background-color: var(--uui-color-danger); color: var(--uui-color-selected-contrast); - height: 0; - transition: height 0.3s ease-out; + max-height: 0; + transition: max-height 0.15s ease-out; + overflow: hidden; } .events.open { - height: auto; + max-height: 500px; + overflow: auto; } .events > div { From 0a110c828a4f633ddb4befd8a2586c900a2185c0 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 15 Feb 2023 15:33:08 +0100 Subject: [PATCH 015/116] wip migrate languages to repositories --- ...repository-detail-data-source.interface.ts | 2 +- .../src/backoffice/backoffice.element.ts | 2 +- .../documents/repository/sources/index.ts | 7 ++ .../backoffice/settings/cultures/manifests.ts | 3 + .../cultures/repository/culture.repository.ts | 29 +++++ .../settings/cultures/repository/manifests.ts | 13 ++ .../repository/sources/culture.server.data.ts | 32 +++++ .../cultures/repository/sources/index.ts | 12 ++ .../src/backoffice/settings/index.ts | 2 + .../settings/languages/language.store.ts | 87 ------------- .../repository/language.repository.ts | 68 ++++++++++ .../languages/repository/language.store.ts | 30 +++++ .../languages/repository/sources/index.ts | 16 +++ .../sources/language.server.data.ts | 116 ++++++++++++++++++ ...root-table-delete-column-layout.element.ts | 6 +- .../language-root-workspace.element.ts | 30 +++-- .../language/language-workspace.context.ts | 85 +++++-------- .../language/language-workspace.element.ts | 8 +- .../workspace-view-language-edit.element.ts | 8 +- .../src/core/mocks/data/languages.data.ts | 2 +- 20 files changed, 388 insertions(+), 170 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/culture.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/sources/culture.server.data.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/sources/index.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/language.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts diff --git a/src/Umbraco.Web.UI.Client/libs/repository/repository-detail-data-source.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/repository-detail-data-source.interface.ts index 04f2894b39..7715d5a61e 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/repository-detail-data-source.interface.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/repository-detail-data-source.interface.ts @@ -5,5 +5,5 @@ export interface RepositoryDetailDataSource { get(key: string): Promise>; insert(data: DetailType): Promise>; update(data: DetailType): Promise>; - trash(key: string): Promise>; + delete(key: string): Promise>; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts index dae0f7ba21..53474073fb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -38,7 +38,7 @@ import { UmbDataTypeTreeStore } from './settings/data-types/repository/data-type import { UmbTemplateTreeStore } from './templating/templates/tree/data/template.tree.store'; import { UmbTemplateDetailStore } from './templating/templates/workspace/data/template.detail.store'; import { UmbThemeContext } from './themes/theme.context'; -import { UmbLanguageStore } from './settings/languages/language.store'; +import { UmbLanguageStore } from './settings/languages/repository/language.store'; import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; import '@umbraco-cms/router'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/index.ts new file mode 100644 index 0000000000..c7cc44fc92 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/index.ts @@ -0,0 +1,7 @@ +import { DocumentModel } from '@umbraco-cms/backend-api'; +import type { DataSourceResponse } from '@umbraco-cms/models'; +import { RepositoryDetailDataSource } from '@umbraco-cms/repository'; + +export interface UmbDocumentDataSource extends RepositoryDetailDataSource { + trash(key: string): Promise>; +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/manifests.ts new file mode 100644 index 0000000000..2bd802ce9c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/manifests.ts @@ -0,0 +1,3 @@ +import { manifests as repositoryManifests } from './repository/manifests'; + +export const manifests = [...repositoryManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/culture.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/culture.repository.ts new file mode 100644 index 0000000000..abdd4e6e72 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/culture.repository.ts @@ -0,0 +1,29 @@ +import { UmbCultureServerDataSource } from './sources/culture.server.data'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbContextConsumerController } from '@umbraco-cms/context-api'; +import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; + +export class UmbCultureRepository { + #init!: Promise; + #host: UmbControllerHostInterface; + + #dataSource: UmbCultureServerDataSource; + + #notificationService?: UmbNotificationService; + + constructor(host: UmbControllerHostInterface) { + this.#host = host; + + this.#dataSource = new UmbCultureServerDataSource(this.#host); + + this.#init = Promise.all([ + new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { + this.#notificationService = instance; + }), + ]); + } + + requestCultures({ skip, take } = { skip: 0, take: 1000 }) { + return this.#dataSource.getCollection({ skip, take }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/manifests.ts new file mode 100644 index 0000000000..0bc2ae18bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/manifests.ts @@ -0,0 +1,13 @@ +import { UmbCultureRepository } from '../repository/culture.repository'; +import { ManifestRepository } from 'libs/extensions-registry/repository.models'; + +export const CULTURE_REPOSITORY_ALIAS = 'Umb.Repository.Cultures'; + +const repository: ManifestRepository = { + type: 'repository', + alias: CULTURE_REPOSITORY_ALIAS, + name: 'Cultures Repository', + class: UmbCultureRepository, +}; + +export const manifests = [repository]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/sources/culture.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/sources/culture.server.data.ts new file mode 100644 index 0000000000..02ac62a9bb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/sources/culture.server.data.ts @@ -0,0 +1,32 @@ +import { UmbCultureDataSource } from '.'; +import { CultureResource } from '@umbraco-cms/backend-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; + +/** + * A data source for the Language that fetches data from the server + * @export + * @class UmbLanguageServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbCultureServerDataSource implements UmbCultureDataSource { + #host: UmbControllerHostInterface; + + /** + * Creates an instance of UmbLanguageServerDataSource. + * @param {UmbControllerHostInterface} host + * @memberof UmbLanguageServerDataSource + */ + constructor(host: UmbControllerHostInterface) { + this.#host = host; + } + + /** + * Get a list of cultures on the server + * @return {*} + * @memberof UmbLanguageServerDataSource + */ + async getCollection({ skip, take }: { skip: number; take: number }) { + return tryExecuteAndNotify(this.#host, CultureResource.getCulture({ skip, take })); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/sources/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/sources/index.ts new file mode 100644 index 0000000000..8b3555ed3f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/cultures/repository/sources/index.ts @@ -0,0 +1,12 @@ +import { PagedCultureModel } from '@umbraco-cms/backend-api'; +import type { DataSourceResponse } from '@umbraco-cms/models'; + +// TODO: This is a temporary solution until we have a proper paging interface +type paging = { + skip: number; + take: number; +}; + +export interface UmbCultureDataSource { + getCollection(paging: paging): Promise>; +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts index de60d18c58..43ef6bfa40 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts @@ -2,6 +2,7 @@ import { manifests as settingsSectionManifests } from './section.manifests'; import { manifests as dashboardManifests } from './dashboards/manifests'; import { manifests as dataTypeManifests } from './data-types/manifests'; import { manifests as extensionManifests } from './extensions/manifests'; +import { manifests as cultureManifests } from './cultures/manifests'; import { manifests as languageManifests } from './languages/manifests'; import { manifests as logviewerManifests } from './logviewer/manifests'; @@ -20,6 +21,7 @@ registerExtensions([ ...dashboardManifests, ...dataTypeManifests, ...extensionManifests, + ...cultureManifests, ...languageManifests, ...logviewerManifests, ]); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/language.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/language.store.ts deleted file mode 100644 index ffc3f178ba..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/language.store.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Observable } from 'rxjs'; -import { CultureModel, CultureResource, LanguageModel, LanguageResource } from '@umbraco-cms/backend-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; -import { UmbStoreBase } from '@umbraco-cms/store'; -import { ArrayState } from '@umbraco-cms/observable-api'; -import { UmbControllerHostInterface } from '@umbraco-cms/controller'; - -export type UmbLanguageStoreItemType = LanguageModel; -export const UMB_LANGUAGE_STORE_CONTEXT_TOKEN = new UmbContextToken('umbLanguageStore'); - -/** - * @export - * @class UmbLanguageStore - * @extends {UmbStoreBase} - * @description - Data Store for languages - */ -export class UmbLanguageStore extends UmbStoreBase { - #data = new ArrayState([], (x) => x.isoCode); - #availableLanguages = new ArrayState([], (x) => x.name); - - public readonly availableLanguages = this.#availableLanguages.asObservable(); - - constructor(host: UmbControllerHostInterface) { - super(host, UMB_LANGUAGE_STORE_CONTEXT_TOKEN.toString()); - } - - getByIsoCode(isoCode: string) { - tryExecuteAndNotify(this._host, LanguageResource.getLanguageByIsoCode({ isoCode })).then(({ data }) => { - if (data) { - this.#data.appendOne(data); - } - }); - - return this.#data.getObservablePart((items) => items.find((item) => item.isoCode === isoCode)); - } - - getAll(): Observable> { - tryExecuteAndNotify(this._host, LanguageResource.getLanguage({ skip: 0, take: 1000 })).then(({ data }) => { - this.#data.append(data?.items ?? []); - }); - - return this.#data; - } - - getAvailableCultures() { - tryExecuteAndNotify(this._host, CultureResource.getCulture({ skip: 0, take: 1000 })).then(({ data }) => { - if (!data) return; - this.#availableLanguages.append(data.items); - }); - - return this.availableLanguages; - } - - async save(language: UmbLanguageStoreItemType): Promise { - if (language.isoCode) { - const { data: updatedLanguage } = await tryExecuteAndNotify( - this._host, - LanguageResource.putLanguageByIsoCode({ isoCode: language.isoCode, requestBody: language }) - ); - if (updatedLanguage) { - this.#data.appendOne(updatedLanguage); - } - } else { - const { data: newLanguage } = await tryExecuteAndNotify( - this._host, - LanguageResource.postLanguage({ requestBody: language }) - ); - if (newLanguage) { - this.#data.appendOne(newLanguage); - } - } - } - - async delete(isoCodes: Array) { - // TODO: revisit this. It looks a bit weird with the nested tryExecuteAndNotify - const queue = isoCodes.map((isoCode) => - tryExecuteAndNotify( - this._host, - tryExecuteAndNotify(this._host, LanguageResource.deleteLanguageByIsoCode({ isoCode })).then(() => isoCode) - ) - ); - const results = await Promise.all(queue); - const filtered = results.filter((x) => !!x).map((result) => result.data); - this.#data.remove(filtered); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts new file mode 100644 index 0000000000..ee727facb9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts @@ -0,0 +1,68 @@ +import { UmbLanguageServerDataSource } from './sources/language.server.data'; +import { UmbLanguageStore, UMB_LANGUAGE_STORE_CONTEXT_TOKEN } from './language.store'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbContextConsumerController } from '@umbraco-cms/context-api'; +import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; +import { ProblemDetailsModel } from '@umbraco-cms/backend-api'; + +export class UmbLanguageRepository { + #init!: Promise; + + #host: UmbControllerHostInterface; + + #detailDataSource: UmbLanguageServerDataSource; + #languageStore?: UmbLanguageStore; + + #notificationService?: UmbNotificationService; + + constructor(host: UmbControllerHostInterface) { + this.#host = host; + + // TODO: figure out how spin up get the correct data source + this.#detailDataSource = new UmbLanguageServerDataSource(this.#host); + + this.#init = Promise.all([ + new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { + this.#notificationService = instance; + }), + + new UmbContextConsumerController(this.#host, UMB_LANGUAGE_STORE_CONTEXT_TOKEN, (instance) => { + this.#languageStore = instance; + }), + ]); + } + + /** + * Creates a new Language scaffold + * @param + * @return {*} + * @memberof UmbLanguageServerDataSource + */ + async createDetailsScaffold() { + return this.#detailDataSource.createScaffold(); + } + + async requestByIsoCode(isoCode: string) { + await this.#init; + + if (!isoCode) { + const error: ProblemDetailsModel = { title: 'Iso code is missing' }; + return { error }; + } + + return this.#detailDataSource.get(isoCode); + } + + async requestLanguages({ skip, take } = { skip: 0, take: 1000 }) { + await this.#init; + + const { data, error } = await this.#detailDataSource.getCollection({ skip, take }); + + if (data) { + // TODO: allow to append an array of items to the store + data.items.forEach((x) => this.#languageStore?.append(x)); + } + + return { data, error, asObservable: () => this.#languageStore!.data }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.store.ts new file mode 100644 index 0000000000..eb5e1c3163 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.store.ts @@ -0,0 +1,30 @@ +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UmbStoreBase } from '@umbraco-cms/store'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { ArrayState } from '@umbraco-cms/observable-api'; +import { LanguageModel } from '@umbraco-cms/backend-api'; + +/** + * @export + * @class UmbLanguageStore + * @extends {UmbStoreBase} + * @description - Details Data Store for Languages + */ +export class UmbLanguageStore extends UmbStoreBase { + #data = new ArrayState([], (x) => x.isoCode); + data = this.#data.asObservable(); + + constructor(host: UmbControllerHostInterface) { + super(host, UmbLanguageStore.name); + } + + append(language: LanguageModel) { + this.#data.append([language]); + } + + remove(uniques: string[]) { + this.#data.remove(uniques); + } +} + +export const UMB_LANGUAGE_STORE_CONTEXT_TOKEN = new UmbContextToken(UmbLanguageStore.name); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/index.ts new file mode 100644 index 0000000000..81efc0303f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/index.ts @@ -0,0 +1,16 @@ +import { LanguageModel, PagedLanguageModel } from '@umbraco-cms/backend-api'; +import type { DataSourceResponse } from '@umbraco-cms/models'; +import { RepositoryDetailDataSource } from '@umbraco-cms/repository'; + +// TODO: This is a temporary solution until we have a proper paging interface +type paging = { + skip: number; + take: number; +}; + +export interface UmbLanguageDataSource extends RepositoryDetailDataSource { + createScaffold(): Promise>; + get(isoCode: string): Promise>; + delete(isoCode: string): Promise>; + getCollection(paging: paging): Promise>; +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts new file mode 100644 index 0000000000..6cc694ba15 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts @@ -0,0 +1,116 @@ +import { ProblemDetailsModel, LanguageResource, LanguageModel } from '@umbraco-cms/backend-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; + +/** + * A data source for the Language that fetches data from the server + * @export + * @class UmbLanguageServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbLanguageServerDataSource implements UmbLanguageServerDataSource { + #host: UmbControllerHostInterface; + + /** + * Creates an instance of UmbLanguageServerDataSource. + * @param {UmbControllerHostInterface} host + * @memberof UmbLanguageServerDataSource + */ + constructor(host: UmbControllerHostInterface) { + this.#host = host; + } + + /** + * Fetches a Language with the given iso code from the server + * @param {string} isoCode + * @return {*} + * @memberof UmbLanguageServerDataSource + */ + async get(isoCode: string) { + if (!isoCode) { + const error: ProblemDetailsModel = { title: 'Iso Code is missing' }; + return { error }; + } + + return tryExecuteAndNotify( + this.#host, + LanguageResource.getLanguageByIsoCode({ + isoCode, + }) + ); + } + + /** + * Creates a new Language scaffold + * @param + * @return {*} + * @memberof UmbLanguageServerDataSource + */ + async createScaffold() { + const data: LanguageModel = { + isoCode: '', + }; + + return { data }; + } + + /** + * Inserts a new Language on the server + * @param {LanguageModel} language + * @return {*} + * @memberof UmbLanguageServerDataSource + */ + async insert(language: LanguageModel) { + if (!language.isoCode) { + const error: ProblemDetailsModel = { title: 'Language iso code is missing' }; + return { error }; + } + + return tryExecuteAndNotify(this.#host, LanguageResource.postLanguage({ requestBody: language })); + } + + /** + * Updates a Language on the server + * @param {LanguageModel} language + * @return {*} + * @memberof UmbLanguageServerDataSource + */ + async update(language: LanguageModel) { + if (!language.isoCode) { + const error: ProblemDetailsModel = { title: 'Language iso code is missing' }; + return { error }; + } + + return tryExecuteAndNotify( + this.#host, + LanguageResource.putLanguageByIsoCode({ isoCode: language.isoCode, requestBody: language }) + ); + } + + /** + * Deletes a Language on the server + * @param {string} isoCode + * @return {*} + * @memberof UmbLanguageServerDataSource + */ + async delete(isoCode: string) { + if (!isoCode) { + const error: ProblemDetailsModel = { title: 'Iso code is missing' }; + return { error }; + } + + return tryExecuteAndNotify( + this.#host, + tryExecuteAndNotify(this.#host, LanguageResource.deleteLanguageByIsoCode({ isoCode })).then(() => isoCode) + ); + } + + /** + * Get a list of Languages on the server + * @return {*} + * @memberof UmbLanguageServerDataSource + */ + async getCollection({ skip, take }: { skip: number; take: number }) { + return tryExecuteAndNotify(this.#host, LanguageResource.getLanguage({ skip, take })); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-table-delete-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-table-delete-column-layout.element.ts index ac3b22a1f4..6ed6993d44 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-table-delete-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-table-delete-column-layout.element.ts @@ -1,7 +1,11 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { UmbLanguageStore, UmbLanguageStoreItemType, UMB_LANGUAGE_STORE_CONTEXT_TOKEN } from '../../language.store'; +import { + UmbLanguageStore, + UmbLanguageStoreItemType, + UMB_LANGUAGE_STORE_CONTEXT_TOKEN, +} from '../../repository/language.store'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../../core/modal'; import { UmbLitElement } from '@umbraco-cms/element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts index 28e918ee79..de838499d4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts @@ -1,7 +1,11 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { UmbLanguageStore, UmbLanguageStoreItemType, UMB_LANGUAGE_STORE_CONTEXT_TOKEN } from '../../language.store'; +import { + UmbLanguageStore, + UmbLanguageStoreItemType, + UMB_LANGUAGE_STORE_CONTEXT_TOKEN, +} from '../../repository/language.store'; import { UmbTableColumn, UmbTableConfig, UmbTableItem } from '../../../../shared/components/table'; import { UmbWorkspaceEntityElement } from '../../../../shared/components/workspace/workspace-entity-element.interface'; import { UmbLitElement } from '@umbraco-cms/element'; @@ -9,6 +13,7 @@ import { UmbLitElement } from '@umbraco-cms/element'; import '../language/language-workspace.element'; import './language-root-table-delete-column-layout.element'; import './language-root-table-name-column-layout.element'; +import { UmbLanguageRepository } from '../../repository/language.repository'; @customElement('umb-language-root-workspace') export class UmbLanguageRootWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement { @@ -65,29 +70,22 @@ export class UmbLanguageRootWorkspaceElement extends UmbLitElement implements Um @state() private _tableItems: Array = []; - #languageStore?: UmbLanguageStore; - - constructor() { - super(); - - this.consumeContext(UMB_LANGUAGE_STORE_CONTEXT_TOKEN, (instance) => { - this.#languageStore = instance; - this.#observeLanguages(); - }); - } + #languageRepository = new UmbLanguageRepository(this); load(): void { - // Not relevant for this workspace + this.#observeLanguages(); } create(): void { // Not relevant for this workspace } - #observeLanguages() { - this.#languageStore?.getAll().subscribe((languages) => { - this.#createTableItems(languages); - }); + async #observeLanguages() { + const { asObservable } = await this.#languageRepository.requestLanguages(); + + if (asObservable) { + this.observe(asObservable(), (languages) => this.#createTableItems(languages)); + } } #createTableItems(languages: Array) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts index abd98b217b..a5a6502a7a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts @@ -1,70 +1,45 @@ -import { UmbLanguageStore, UmbLanguageStoreItemType, UMB_LANGUAGE_STORE_CONTEXT_TOKEN } from '../../language.store'; +import { UmbLanguageRepository } from '../../repository/language.repository'; +import type { LanguageModel } from '@umbraco-cms/backend-api'; +import { ObjectState } from '@umbraco-cms/observable-api'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; -import { ObjectState, UmbObserverController } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerController } from '@umbraco-cms/context-api'; +import { UmbWorkspaceContext } from 'src/backoffice/shared/components/workspace/workspace-context/workspace-context'; -const DefaultLanguageData: UmbLanguageStoreItemType = { - name: '', - isoCode: '', - isDefault: false, - isMandatory: false, -}; +export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { + #host: UmbControllerHostInterface; + #data = new ObjectState(undefined); + #languageRepository: UmbLanguageRepository; + #isNew = false; -export class UmbWorkspaceLanguageContext { - public host: UmbControllerHostInterface; - - #entityKey: string | null; - - #data; - public readonly data; - - #store: UmbLanguageStore | null = null; - protected _storeObserver?: UmbObserverController; - - constructor(host: UmbControllerHostInterface, entityKey: string | null) { - this.host = host; - this.#entityKey = entityKey; - - this.#data = new ObjectState(DefaultLanguageData); - this.data = this.#data.asObservable(); - - new UmbContextConsumerController(host, UMB_LANGUAGE_STORE_CONTEXT_TOKEN, (_instance: UmbLanguageStore) => { - this.#store = _instance; - this.#observeStore(); - }); + constructor(host: UmbControllerHostInterface) { + super(host); + this.#host = host; + this.#languageRepository = new UmbLanguageRepository(this.#host); } - #observeStore(): void { - if (!this.#store || this.#entityKey === null) { - return; + async load(isoCode: string) { + const { data } = await this.#languageRepository.requestByIsoCode(isoCode); + if (data) { + this.#isNew = false; + this.#data.update(data); } - - this._storeObserver?.destroy(); - this._storeObserver = new UmbObserverController(this.host, this.#store.getByIsoCode(this.#entityKey), (content) => { - if (!content) return; // TODO: Handle nicely if there is no content data. - this.update(content); - }); } - public getData() { + async createScaffold() { + const { data } = await this.#languageRepository.createDetailsScaffold(); + if (!data) return; + this.#isNew = true; + this.#data.update(data); + } + + getData() { return this.#data.getValue(); } - public getAvailableCultures() { - //TODO: Don't use !, however this will be changed with the introduction of repositories. - return this.#store!.getAvailableCultures(); + getEntityType() { + return 'language'; } - public update(data: Partial) { - this.#data.next({ ...this.getData(), ...data }); - } - - public save(): Promise { - if (!this.#store) { - // TODO: more beautiful error: - console.error('Could not save cause workspace context has no store.'); - return Promise.resolve(); - } - return this.#store.save(this.getData()); + public destroy(): void { + this.#data.complete(); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts index 7bf01a12ef..28a2ccc6db 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts @@ -2,9 +2,9 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; -import { UmbLanguageStoreItemType } from '../../language.store'; +import { UmbLanguageStoreItemType } from '../../repository/language.store'; import { UmbWorkspaceEntityElement } from '../../../../shared/components/workspace/workspace-entity-element.interface'; -import { UmbWorkspaceLanguageContext } from './language-workspace.context'; +import { UmbLanguageWorkspaceContext } from './language-workspace.context'; import { UmbLitElement } from '@umbraco-cms/element'; import '../../../../shared/components/workspace/workspace-action/save/workspace-action-node-save.element.ts'; @@ -28,7 +28,7 @@ export class UmbLanguageWorkspaceElement extends UmbLitElement implements UmbWor @property() language?: UmbLanguageStoreItemType; - #languageWorkspaceContext?: UmbWorkspaceLanguageContext; + #languageWorkspaceContext?: UmbLanguageWorkspaceContext; load(key: string): void { this.provideLanguageWorkspaceContext(key); @@ -39,7 +39,7 @@ export class UmbLanguageWorkspaceElement extends UmbLitElement implements UmbWor } public provideLanguageWorkspaceContext(entityKey: string | null) { - this.#languageWorkspaceContext = new UmbWorkspaceLanguageContext(this, entityKey); + this.#languageWorkspaceContext = new UmbLanguageWorkspaceContext(this, entityKey); this.provideContext('umbWorkspaceContext', this.#languageWorkspaceContext); this.#languageWorkspaceContext.data.subscribe((language) => { this.language = language; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/workspace-view-language-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/workspace-view-language-edit.element.ts index 831f403ca0..0b134088ec 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/workspace-view-language-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/workspace-view-language-edit.element.ts @@ -4,12 +4,12 @@ import { css, html, nothing } from 'lit'; import { repeat } from 'lit/directives/repeat.js'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; -import { UmbWorkspaceLanguageContext } from '../../language-workspace.context'; +import { UmbLanguageWorkspaceContext } from '../../language-workspace.context'; import { UmbLanguageStore, UmbLanguageStoreItemType, UMB_LANGUAGE_STORE_CONTEXT_TOKEN, -} from '../../../../language.store'; +} from '../../../../repository/language.store'; import { UmbLitElement } from '@umbraco-cms/element'; import { CultureModel, LanguageModel } from '@umbraco-cms/backend-api'; @@ -64,12 +64,12 @@ export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { @state() private _startData: LanguageModel | null = null; - #languageWorkspaceContext?: UmbWorkspaceLanguageContext; + #languageWorkspaceContext?: UmbLanguageWorkspaceContext; constructor() { super(); - this.consumeContext('umbWorkspaceContext', (instance) => { + this.consumeContext('umbWorkspaceContext', (instance) => { this.#languageWorkspaceContext = instance; if (!this.#languageWorkspaceContext) return; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts index f693df0fee..2b1f16d2b1 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts @@ -1,4 +1,4 @@ -import { UmbLanguageStoreItemType } from '../../../backoffice/settings/languages/language.store'; +import { UmbLanguageStoreItemType } from '../../../backoffice/settings/languages/repository/language.store'; import { UmbData } from './data'; // Temp mocked database From c38a533c4e60f7a310fe9dc51eeaff4b892cfe70 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 15 Feb 2023 16:28:41 +0100 Subject: [PATCH 016/116] load language and subscribe to draft data --- .../language/language-workspace.context.ts | 4 ++- .../language/language-workspace.element.ts | 32 +++++++++---------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts index a5a6502a7a..0a3c3ed935 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts @@ -6,10 +6,12 @@ import { UmbWorkspaceContext } from 'src/backoffice/shared/components/workspace/ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { #host: UmbControllerHostInterface; - #data = new ObjectState(undefined); #languageRepository: UmbLanguageRepository; #isNew = false; + #data = new ObjectState(undefined); + data = this.#data.asObservable(); + constructor(host: UmbControllerHostInterface) { super(host); this.#host = host; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts index 28a2ccc6db..4ad4c78bb7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts @@ -1,12 +1,12 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; +import { customElement, state } from 'lit/decorators.js'; import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; -import { UmbLanguageStoreItemType } from '../../repository/language.store'; import { UmbWorkspaceEntityElement } from '../../../../shared/components/workspace/workspace-entity-element.interface'; import { UmbLanguageWorkspaceContext } from './language-workspace.context'; import { UmbLitElement } from '@umbraco-cms/element'; import '../../../../shared/components/workspace/workspace-action/save/workspace-action-node-save.element.ts'; +import { LanguageModel } from '@umbraco-cms/backend-api'; @customElement('umb-language-workspace') export class UmbLanguageWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement { @@ -25,24 +25,24 @@ export class UmbLanguageWorkspaceElement extends UmbLitElement implements UmbWor `, ]; - @property() - language?: UmbLanguageStoreItemType; + @state() + _language?: LanguageModel; - #languageWorkspaceContext?: UmbLanguageWorkspaceContext; + #languageWorkspaceContext = new UmbLanguageWorkspaceContext(this); load(key: string): void { - this.provideLanguageWorkspaceContext(key); + this.#languageWorkspaceContext.load(key); } - create(parentKey: string | null): void { - this.provideLanguageWorkspaceContext(parentKey); + create(): void { + this.#languageWorkspaceContext.createScaffold(); } - public provideLanguageWorkspaceContext(entityKey: string | null) { - this.#languageWorkspaceContext = new UmbLanguageWorkspaceContext(this, entityKey); - this.provideContext('umbWorkspaceContext', this.#languageWorkspaceContext); - this.#languageWorkspaceContext.data.subscribe((language) => { - this.language = language; + async connectedCallback() { + super.connectedCallback(); + + this.observe(this.#languageWorkspaceContext.data, (data) => { + this._language = data; }); } @@ -51,13 +51,13 @@ export class UmbLanguageWorkspaceElement extends UmbLitElement implements UmbWor const target = event.composedPath()[0] as UUIInputElement; if (typeof target?.value === 'string') { - this.#languageWorkspaceContext?.update({ name: target.value }); + this.#languageWorkspaceContext?.setName(target.value); } } } render() { - if (!this.language) return nothing; + if (!this._language) return nothing; return html` @@ -65,7 +65,7 @@ export class UmbLanguageWorkspaceElement extends UmbLitElement implements UmbWor - + `; From c2fab69600a7424a86a7f56c494aaefaffd63d3b Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 15 Feb 2023 16:42:50 +0000 Subject: [PATCH 017/116] Adds way to open debug info in a dialog * UI needs tidying up - spacing * Consider dropping the other UI approach and use dialog only * Need to ensure all typed * Neaten/refactor code if needed * Lint code --- .../modal-views/fields-viewer.element.ts | 6 +- .../dashboard-published-status.element.ts | 2 +- .../shared/components/debug/debug.element.ts | 115 ++++++++---- .../components/debug/debug.modal.element.ts | 163 ++++++++++++++++++ .../src/backoffice/shared/components/index.ts | 3 +- 5 files changed, 248 insertions(+), 41 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/modal-views/fields-viewer.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/modal-views/fields-viewer.element.ts index 6e81567c79..e55b6b4b22 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/modal-views/fields-viewer.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/modal-views/fields-viewer.element.ts @@ -16,11 +16,7 @@ export class UmbModalLayoutFieldsViewerElement extends UmbModalLayoutElement
    +

    ${this._publishedStatusText}

    { + this._modalService = modalService; + }); + } + connectedCallback(): void { super.connectedCallback(); // Dispatch it this.dispatchEvent( - new UmbContextDebugRequest((instances: Map) => { - console.log('I have contexts now', instances); + new UmbContextDebugRequest((contexts: Map) => { - this.contextAliases = instances; + // The Contexts are collected + // When travelling up through the DOM from this element + // to the root of which then uses the callback prop + // of the this event tha has been raised to assign the contexts + // back to this property of the WebComponent + this.contexts = contexts; }) ); } render() { - if (this.enabled) { - return html` -
    - - - Debug - + if (this.enabled) { + return this.useDialog ? this._renderDialog() : this._renderPanel(); + } else { + return nothing; + } + + } -
    -
    -

    Context Aliases to consume

    -
      - ${this._renderContextAliases()} -
    -
    + private _toggleDebugPane() { + this._debugPaneOpen = !this._debugPaneOpen; + } + + private _openDialog() { + const modalHandler = this._modalService?.open('umb-debug-modal-layout', { size: 'medium', type: 'sidebar', data:{ contexts: this.contexts }}); + + modalHandler?.onClose().then((data) => { + // if any data is supplied on close, it will be available here. + console.log('modal closed data', data); + }); + } + + + + private _renderDialog() { + return html` +
    + + Debug + +
    `; + } + + private _renderPanel(){ + return html` +
    + + + Debug + + +
    +
    +

    Context Aliases to consume

    +
      + ${this._renderContextAliases()} +
    - `; - } - - return nothing; +
    `; } private _renderContextAliases() { const aliases = []; - for (const [alias, instance] of this.contextAliases) { + for (const [alias, instance] of this.contexts) { aliases.push( html`
  • Context: ${alias} @@ -124,10 +175,6 @@ export class UmbDebug extends LitElement { if (key.startsWith('_')) { continue; } - // Goes KABOOM - if try to loop over the class/object - // instanceKeys.push(html`
  • ${key} = ${instance[key]}
  • `); - - // console.log(`key: ${key} = ${value} TYPEOF: ${typeof value}`); const value = instance[key]; if (typeof value === 'string') { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts new file mode 100644 index 0000000000..56f898c6d5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts @@ -0,0 +1,163 @@ +import { css, html, nothing, TemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { UUITextStyles } from '@umbraco-ui/uui-css'; +import { UmbModalHandler, UmbModalLayoutElement } from '@umbraco-cms/modal'; + +@customElement('umb-debug-modal-layout') +export class UmbDebugModalLayout extends UmbModalLayoutElement { + static styles = [ + UUITextStyles, + css` + uui-dialog-layout { + display: flex; + flex-direction: column; + height: 100%; + + padding: var(--uui-size-space-5); + box-sizing: border-box; + } + + uui-scroll-container { + overflow-y: scroll; + max-height: 100%; + min-height: 0; + flex: 1; + } + + uui-icon { + vertical-align: text-top; + color: var(--uui-color-danger); + } + + .context { + padding:15px 0; + border-bottom:1px solid var(--uui-color-danger-emphasis); + } + + h3 { + margin-top: 0; + margin-bottom: 0; + } + + h3 > span { + border-radius: var(--uui-size-4); + background-color: var(--uui-color-danger); + color: var(--uui-color-danger-contrast); + padding: 8px; + font-size: 12px; + } + + + ul { + margin-top: 0; + } + `, + ]; + + + // the modal handler will be injected into the element when the modal is opened. + @property({ attribute: false }) + modalHandler?: UmbModalHandler; + + private _handleClose() { + /* Optional data of any type can be applied to the close method to pass it + to the modal parent through the onClose promise. */ + //this.modalHandler?.close('MY DATA'); + this.modalHandler?.close(); + } + + render() { + return html` + + + Debug: Contexts + + + ${this._renderContextAliases()} + + Close + + `; + } + + private _renderContextAliases() { + if(!this.data) { + return nothing; + } + + const aliases = []; + for (const [alias, instance] of this.data.contexts) { + aliases.push( + html` +
    +

    ${alias} ${typeof instance}

    + ${this._renderInstance(instance)} +
    ` + ); + } + + return aliases; + } + + private _renderInstance(instance: any) { + const instanceKeys: TemplateResult[] = []; + + if (typeof instance === 'function') { + return instanceKeys.push(html`
  • Callable Function
  • `); + } else if (typeof instance === 'object') { + const methodNames = this.getClassMethodNames(instance); + if (methodNames.length) { + instanceKeys.push( + html` +

    Methods

    +
      + ${methodNames.map((methodName) => html`
    • ${methodName}
    • `)} +
    + `); + } + + instanceKeys.push(html`

    Properties

    `); + + for (const key in instance) { + if (key.startsWith('_')) { + continue; + } + + const value = instance[key]; + if (typeof value === 'string') { + instanceKeys.push(html`
  • ${key} = ${value}
  • `); + } else { + instanceKeys.push(html`
  • ${key} Type (${typeof value})
  • `); + } + } + } else { + instanceKeys.push(html`
  • Context is a primitive with value: ${instance}
  • `); + } + + return instanceKeys; + } + + + private getClassMethodNames(klass: any) { + const isGetter = (x: any, name: string): boolean => !!(Object.getOwnPropertyDescriptor(x, name) || {}).get; + const isFunction = (x: any, name: string): boolean => typeof x[name] === 'function'; + const deepFunctions = (x: any): any => + x !== Object.prototype && + Object.getOwnPropertyNames(x) + .filter((name) => isGetter(x, name) || isFunction(x, name)) + .concat(deepFunctions(Object.getPrototypeOf(x)) || []); + const distinctDeepFunctions = (klass: any) => Array.from(new Set(deepFunctions(klass))); + + const allMethods = + typeof klass.prototype === 'undefined' + ? distinctDeepFunctions(klass) + : Object.getOwnPropertyNames(klass.prototype); + return allMethods.filter((name: any) => name !== 'constructor' && !name.startsWith('_')); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-debug-modal-layout': UmbDebugModalLayout; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts index c9602d9759..b09a13320e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts @@ -31,4 +31,5 @@ import './input-document-picker/input-document-picker.element'; import './empty-state/empty-state.element'; import './color-picker/color-picker.element'; -import './debug/debug.element'; \ No newline at end of file +import './debug/debug.element'; +import './debug/debug.modal.element'; \ No newline at end of file From 92ca98559351cff57f1177f9bccb840eb99c3d07 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 15 Feb 2023 20:49:05 +0100 Subject: [PATCH 018/116] add methods to update data --- .../language/language-workspace.context.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts index 0a3c3ed935..2cb542b90a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts @@ -41,7 +41,23 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { return 'language'; } - public destroy(): void { + setName(name: string) { + this.#data.update({ name }); + } + + setMandatory(isMandatory: boolean) { + this.#data.update({ isMandatory }); + } + + setDefault(isDefault: boolean) { + this.#data.update({ isDefault }); + } + + setFallbackLanguage(isoCode: string) { + this.#data.update({ fallbackIsoCode: isoCode }); + } + + destroy(): void { this.#data.complete(); } } From cb4aa5e126d6b99c0d81cac6549dfdfda3c7d808 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 15 Feb 2023 20:49:26 +0100 Subject: [PATCH 019/116] rename file + use methods on context --- .../languages/workspace/language/manifests.ts | 2 +- ....ts => edit-language-workspace-view.element.ts} | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) rename src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/{workspace-view-language-edit.element.ts => edit-language-workspace-view.element.ts} (95%) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts index 1c686ba026..fbde1593b7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts @@ -30,7 +30,7 @@ const workspaceViews: Array = [ type: 'workspaceView', alias: 'Umb.WorkspaceView.Language.Edit', name: 'Language Workspace Edit View', - loader: () => import('./views/edit/workspace-view-language-edit.element'), + loader: () => import('./views/edit/edit-language-workspace-view.element'), weight: 90, meta: { workspaces: ['Umb.Workspace.Language'], diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/workspace-view-language-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts similarity index 95% rename from src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/workspace-view-language-edit.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 0b134088ec..54486c6929 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/workspace-view-language-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -72,14 +72,8 @@ export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { this.consumeContext('umbWorkspaceContext', (instance) => { this.#languageWorkspaceContext = instance; - if (!this.#languageWorkspaceContext) return; - this.observe(this.#languageWorkspaceContext.data, (language) => { this.language = language; - - if (this._startData === null) { - this._startData = language; - } }); this.observe(this.#languageWorkspaceContext.getAvailableCultures(), (cultures) => { this._availableCultures = cultures; @@ -133,23 +127,21 @@ export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { #handleDefaultChange(event: UUIBooleanInputEvent) { if (event instanceof UUIBooleanInputEvent) { const target = event.composedPath()[0] as UUIToggleElement; - - this.#languageWorkspaceContext?.update({ isDefault: target.checked }); + this.#languageWorkspaceContext?.setDefault(target.checked); } } #handleMandatoryChange(event: UUIBooleanInputEvent) { if (event instanceof UUIBooleanInputEvent) { const target = event.composedPath()[0] as UUIToggleElement; - - this.#languageWorkspaceContext?.update({ isMandatory: target.checked }); + this.#languageWorkspaceContext?.setMandatory(target.checked); } } #handleFallbackChange(event: UUIComboboxEvent) { if (event instanceof UUIComboboxEvent) { const target = event.composedPath()[0] as UUIComboboxElement; - this.#languageWorkspaceContext?.update({ fallbackIsoCode: target.value.toString() }); + this.#languageWorkspaceContext?.setFallbackLanguage(target.value.toString()); } } From 76aa423eadedcb12c0fadb75ff020a0a30321a25 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 15 Feb 2023 22:00:24 +0100 Subject: [PATCH 020/116] add crud methods to repo --- .../repository/language.repository.ts | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts index ee727facb9..252b7bb753 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts @@ -3,7 +3,7 @@ import { UmbLanguageStore, UMB_LANGUAGE_STORE_CONTEXT_TOKEN } from './language.s import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; -import { ProblemDetailsModel } from '@umbraco-cms/backend-api'; +import { LanguageModel, ProblemDetailsModel } from '@umbraco-cms/backend-api'; export class UmbLanguageRepository { #init!: Promise; @@ -65,4 +65,52 @@ export class UmbLanguageRepository { return { data, error, asObservable: () => this.#languageStore!.data }; } + + async update(language: LanguageModel) { + await this.#init; + + const { data, error } = await this.#detailDataSource.update(language); + + if (data) { + const notification = { data: { message: `Language saved` } }; + this.#notificationService?.peek('positive', notification); + this.#languageStore?.append(data); + } + + return { data, error }; + } + + async create(language: LanguageModel) { + await this.#init; + + const { data, error } = await this.#detailDataSource.update(language); + + if (data) { + this.#languageStore?.append(data); + const notification = { data: { message: `Language created` } }; + this.#notificationService?.peek('positive', notification); + } + + return { data, error }; + } + + async delete(key: string) { + await this.#init; + + if (!key) { + const error: ProblemDetailsModel = { title: 'Language key is missing' }; + return { error }; + } + + const { error } = await this.#detailDataSource.delete(key); + + if (!error) { + const notification = { data: { message: `Language deleted` } }; + this.#notificationService?.peek('positive', notification); + } + + this.#languageStore?.remove([key]); + + return { error }; + } } From e87bbe6e127140dc45d939ddfdbe8b9021125400 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 15 Feb 2023 22:00:44 +0100 Subject: [PATCH 021/116] use new workspace action --- .../languages/workspace/language/manifests.ts | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts index fbde1593b7..157ef29fc5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts @@ -1,3 +1,4 @@ +import { UmbSaveWorkspaceAction } from '../../../../shared/workspace-actions/save.action'; import type { ManifestWorkspace, ManifestWorkspaceAction, ManifestWorkspaceView } from '@umbraco-cms/models'; const workspace: ManifestWorkspace = { @@ -10,21 +11,6 @@ const workspace: ManifestWorkspace = { }, }; -const workspaceActions: Array = [ - { - type: 'workspaceAction', - alias: 'Umb.WorkspaceAction.Language.Save', - name: 'Save Language Workspace Action', - loader: () => - import('../../../../shared/components/workspace/workspace-action/save/workspace-action-node-save.element'), - meta: { - workspaces: ['Umb.Workspace.Language'], - look: 'primary', - color: 'positive', - }, - }, -]; - const workspaceViews: Array = [ { type: 'workspaceView', @@ -41,4 +27,20 @@ const workspaceViews: Array = [ }, ]; +const workspaceActions: Array = [ + { + type: 'workspaceAction', + alias: 'Umb.WorkspaceAction.Language.Save', + name: 'Save Language Workspace Action', + meta: { + workspaces: ['Umb.Workspace.Language'], + look: 'primary', + color: 'positive', + label: 'Save', + repositoryAlias: 'Umb.Repository.Language', + api: UmbSaveWorkspaceAction, + }, + }, +]; + export const manifests = [workspace, ...workspaceViews, ...workspaceActions]; From 95273e55e72f157751452cd8d8e63cfe3c0d4e68 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 15 Feb 2023 22:01:28 +0100 Subject: [PATCH 022/116] clean up --- .../language-root-workspace.element.ts | 21 +++----- .../language/language-workspace.context.ts | 14 +++-- .../language/language-workspace.element.ts | 17 +++---- .../edit-language-workspace-view.element.ts | 51 ++++++++----------- 4 files changed, 45 insertions(+), 58 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts index de838499d4..557c30e9b5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts @@ -1,22 +1,16 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { - UmbLanguageStore, - UmbLanguageStoreItemType, - UMB_LANGUAGE_STORE_CONTEXT_TOKEN, -} from '../../repository/language.store'; import { UmbTableColumn, UmbTableConfig, UmbTableItem } from '../../../../shared/components/table'; -import { UmbWorkspaceEntityElement } from '../../../../shared/components/workspace/workspace-entity-element.interface'; +import { UmbLanguageRepository } from '../../repository/language.repository'; import { UmbLitElement } from '@umbraco-cms/element'; +import { LanguageModel } from '@umbraco-cms/backend-api'; -import '../language/language-workspace.element'; import './language-root-table-delete-column-layout.element'; import './language-root-table-name-column-layout.element'; -import { UmbLanguageRepository } from '../../repository/language.repository'; @customElement('umb-language-root-workspace') -export class UmbLanguageRootWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement { +export class UmbLanguageRootWorkspaceElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -72,14 +66,11 @@ export class UmbLanguageRootWorkspaceElement extends UmbLitElement implements Um #languageRepository = new UmbLanguageRepository(this); - load(): void { + connectedCallback() { + super.connectedCallback(); this.#observeLanguages(); } - create(): void { - // Not relevant for this workspace - } - async #observeLanguages() { const { asObservable } = await this.#languageRepository.requestLanguages(); @@ -88,7 +79,7 @@ export class UmbLanguageRootWorkspaceElement extends UmbLitElement implements Um } } - #createTableItems(languages: Array) { + #createTableItems(languages: Array) { this._tableItems = languages.map((language) => { return { key: language.isoCode ?? '', diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts index 2cb542b90a..9182536bcd 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts @@ -1,13 +1,13 @@ import { UmbLanguageRepository } from '../../repository/language.repository'; +import { UmbWorkspaceContext } from '../../../../shared/components/workspace/workspace-context/workspace-context'; import type { LanguageModel } from '@umbraco-cms/backend-api'; import { ObjectState } from '@umbraco-cms/observable-api'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; -import { UmbWorkspaceContext } from 'src/backoffice/shared/components/workspace/workspace-context/workspace-context'; export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { #host: UmbControllerHostInterface; #languageRepository: UmbLanguageRepository; - #isNew = false; + isNew = false; #data = new ObjectState(undefined); data = this.#data.asObservable(); @@ -21,7 +21,7 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { async load(isoCode: string) { const { data } = await this.#languageRepository.requestByIsoCode(isoCode); if (data) { - this.#isNew = false; + this.isNew = false; this.#data.update(data); } } @@ -29,7 +29,7 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { async createScaffold() { const { data } = await this.#languageRepository.createDetailsScaffold(); if (!data) return; - this.#isNew = true; + this.isNew = true; this.#data.update(data); } @@ -45,6 +45,10 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { this.#data.update({ name }); } + setCulture(isoCode: string) { + this.#data.update({ isoCode }); + } + setMandatory(isMandatory: boolean) { this.#data.update({ isMandatory }); } @@ -53,7 +57,7 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { this.#data.update({ isDefault }); } - setFallbackLanguage(isoCode: string) { + setFallbackCulture(isoCode: string) { this.#data.update({ fallbackIsoCode: isoCode }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts index 4ad4c78bb7..48f9bb6d9b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts @@ -5,7 +5,6 @@ import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; import { UmbWorkspaceEntityElement } from '../../../../shared/components/workspace/workspace-entity-element.interface'; import { UmbLanguageWorkspaceContext } from './language-workspace.context'; import { UmbLitElement } from '@umbraco-cms/element'; -import '../../../../shared/components/workspace/workspace-action/save/workspace-action-node-save.element.ts'; import { LanguageModel } from '@umbraco-cms/backend-api'; @customElement('umb-language-workspace') @@ -30,6 +29,14 @@ export class UmbLanguageWorkspaceElement extends UmbLitElement implements UmbWor #languageWorkspaceContext = new UmbLanguageWorkspaceContext(this); + constructor() { + super(); + + this.observe(this.#languageWorkspaceContext.data, (data) => { + this._language = data; + }); + } + load(key: string): void { this.#languageWorkspaceContext.load(key); } @@ -38,14 +45,6 @@ export class UmbLanguageWorkspaceElement extends UmbLitElement implements UmbWor this.#languageWorkspaceContext.createScaffold(); } - async connectedCallback() { - super.connectedCallback(); - - this.observe(this.#languageWorkspaceContext.data, (data) => { - this._language = data; - }); - } - #handleInput(event: UUIInputEvent) { if (event instanceof UUIInputEvent) { const target = event.composedPath()[0] as UUIInputElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 54486c6929..88281fa10b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -5,16 +5,12 @@ import { repeat } from 'lit/directives/repeat.js'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { UmbLanguageWorkspaceContext } from '../../language-workspace.context'; -import { - UmbLanguageStore, - UmbLanguageStoreItemType, - UMB_LANGUAGE_STORE_CONTEXT_TOKEN, -} from '../../../../repository/language.store'; +import { UmbCultureRepository } from '../../../../../cultures/repository/culture.repository'; import { UmbLitElement } from '@umbraco-cms/element'; import { CultureModel, LanguageModel } from '@umbraco-cms/backend-api'; -@customElement('umb-workspace-view-language-edit') -export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { +@customElement('umb-edit-language-workspace-view') +export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -50,13 +46,13 @@ export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { ]; @property() - language?: UmbLanguageStoreItemType; + language?: LanguageModel; @state() - private _languages: UmbLanguageStoreItemType[] = []; + private _languages: LanguageModel[] = []; @state() - private _availableCultures: CultureModel[] = []; + private _cultures: CultureModel[] = []; @state() private _search = ''; @@ -65,6 +61,7 @@ export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { private _startData: LanguageModel | null = null; #languageWorkspaceContext?: UmbLanguageWorkspaceContext; + #cultureRepository = new UmbCultureRepository(this); constructor() { super(); @@ -75,18 +72,14 @@ export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { this.observe(this.#languageWorkspaceContext.data, (language) => { this.language = language; }); - this.observe(this.#languageWorkspaceContext.getAvailableCultures(), (cultures) => { - this._availableCultures = cultures; - }); }); + } - this.consumeContext(UMB_LANGUAGE_STORE_CONTEXT_TOKEN, (instance: UmbLanguageStore) => { - if (!instance) return; - - instance.getAll().subscribe((languages: Array) => { - this._languages = languages; - }); - }); + protected async firstUpdated() { + const { data } = await this.#cultureRepository.requestCultures(); + if (data) { + this._cultures = data.items; + } } #handleLanguageChange(event: Event) { @@ -95,13 +88,13 @@ export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { const isoCode = target.value.toString(); if (isoCode) { - this.#languageWorkspaceContext?.update({ isoCode }); + this.#languageWorkspaceContext?.setCulture(isoCode); // If the language name is not set, we set it to the name of the selected language. if (!this.language?.name) { - const language = this._availableCultures.find((culture) => culture.name === isoCode); - if (language) { - this.#languageWorkspaceContext?.update({ name: language.name }); + const culture = this._cultures.find((culture) => culture.name === isoCode); + if (culture && culture.englishName) { + this.#languageWorkspaceContext?.setName(culture.englishName); } } } else { @@ -141,12 +134,12 @@ export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { #handleFallbackChange(event: UUIComboboxEvent) { if (event instanceof UUIComboboxEvent) { const target = event.composedPath()[0] as UUIComboboxElement; - this.#languageWorkspaceContext?.setFallbackLanguage(target.value.toString()); + this.#languageWorkspaceContext?.setFallbackCulture(target.value.toString()); } } get #filteredCultures(): Array { - return this._availableCultures.filter((culture) => { + return this._cultures.filter((culture) => { return culture.englishName?.toLowerCase().includes(this._search.toLowerCase()); }); } @@ -162,7 +155,7 @@ export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { } get #fromAvailableCultures() { - return this._availableCultures.find((culture) => culture.name === this.language?.isoCode); + return this._cultures.find((culture) => culture.name === this.language?.isoCode); } #renderCultureWarning() { @@ -259,10 +252,10 @@ export class UmbWorkspaceViewLanguageEditElement extends UmbLitElement { } } -export default UmbWorkspaceViewLanguageEditElement; +export default UmbEditLanguageWorkspaceViewElement; declare global { interface HTMLElementTagNameMap { - 'umb-workspace-view-language-edit': UmbWorkspaceViewLanguageEditElement; + 'umb-edit-language-workspace-view': UmbEditLanguageWorkspaceViewElement; } } From 69722aa3003c9792bd6a29d6f7d017b137f81cfd Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 15 Feb 2023 22:12:38 +0100 Subject: [PATCH 023/116] register delete language entity action + language repository --- .../languages/entity-actions/manifests.ts | 22 +++++++++++++++++++ .../settings/languages/manifests.ts | 4 +++- .../languages/repository/manifests.ts | 13 +++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/entity-actions/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/entity-actions/manifests.ts new file mode 100644 index 0000000000..84b57075b0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/entity-actions/manifests.ts @@ -0,0 +1,22 @@ +import { UmbDeleteEntityAction } from '../../../shared/entity-actions/delete/delete.action'; +import { ManifestEntityAction } from '@umbraco-cms/extensions-registry'; + +const entityType = 'language'; +const repositoryAlias = 'Umb.Repository.Languages'; + +const entityActions: Array = [ + { + type: 'entityAction', + alias: 'Umb.EntityAction.Language.Delete', + name: 'Delete Language Entity Action', + meta: { + entityType, + repositoryAlias, + icon: 'umb:trash', + label: 'Delete', + api: UmbDeleteEntityAction, + }, + }, +]; + +export const manifests = [...entityActions]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/manifests.ts index 450deac4e9..c7e92f8c72 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/manifests.ts @@ -1,4 +1,6 @@ +import { manifests as repositoryManifests } from './repository/manifests'; import { manifests as treeManifests } from './sidebar-menu-item/manifests'; +import { manifests as entityActions } from './entity-actions/manifests'; import { manifests as workspaceManifests } from './workspace/manifests'; -export const manifests = [...treeManifests, ...workspaceManifests]; +export const manifests = [...repositoryManifests, ...entityActions, ...treeManifests, ...workspaceManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts new file mode 100644 index 0000000000..7020f13c22 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts @@ -0,0 +1,13 @@ +import { UmbLanguageRepository } from '../repository/language.repository'; +import { ManifestRepository } from 'libs/extensions-registry/repository.models'; + +export const LANGUAGE_REPOSITORY_ALIAS = 'Umb.Repository.Languages'; + +const repository: ManifestRepository = { + type: 'repository', + alias: LANGUAGE_REPOSITORY_ALIAS, + name: 'Languages Repository', + class: UmbLanguageRepository, +}; + +export const manifests = [repository]; From ee46ae2084e092d20c580d2e2293b504a1f2786d Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Thu, 16 Feb 2023 09:50:27 +0100 Subject: [PATCH 024/116] multiurl --- .../input-media-picker.element.ts | 5 +- .../input-multi-url-picker.element.ts | 49 ++++++- .../src/core/mocks/data/data-type.data.ts | 2 +- .../modal-layout-multi-url-picker.element.ts | 121 ++++++++++++------ 4 files changed, 128 insertions(+), 49 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-media-picker/input-media-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-media-picker/input-media-picker.element.ts index 90e4c0e49d..381726055c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-media-picker/input-media-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-media-picker/input-media-picker.element.ts @@ -21,8 +21,9 @@ export class UmbInputMediaPickerElement extends FormControlMixin(UmbLitElement) } #add-button { text-align: center; - min-height: 160px; + height: 202px; } + uui-icon { display: block; margin: 0 auto; @@ -168,7 +169,7 @@ export class UmbInputMediaPickerElement extends FormControlMixin(UmbLitElement) return html` ${this._items?.map((item) => this._renderItem(item))} ${this._renderButton()} `; } private _renderButton() { - if (this.max == 1 && this._items && this._items.length > 0) return; + if (this._items && this.max && this._items.length >= this.max) return; return html` Add diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts index 9235d780b3..38bb0cb82d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts @@ -8,6 +8,11 @@ import { DocumentTreeItemModel, FolderTreeItemModel } from '@umbraco-cms/backend import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; export type OverlaySize = 'small' | 'medium' | 'large'; +export interface Url { + title: string; + href: string; + target: boolean; +} @customElement('umb-input-multi-url-picker') export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElement) { @@ -90,6 +95,24 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen } } + @property() + target = false; + + @property() + title = ''; + + @property() + url = ''; + + @state() + private _urls?: Url[] = [ + { + title: 'Cake', + href: 'google.com', + target: true, + }, + ]; + @state() private _items?: Array; @@ -115,28 +138,40 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen } private _openPicker() { - const modalHandler = this._modalService?.multiUrlPicker(); + const modalHandler = this._modalService?.multiUrlPicker({ treeItem: this._selectedKeys[0], target: this.target }); modalHandler?.onClose().then(({ selection }: any) => { - //this._setSelection(selection); - console.log(selection); + this.selectedKeys[0] = selection.treeItem; + this.target = selection.target; + this.url = selection.href; }); } render() { - return html`${this._items?.map((item) => this._renderItem(item))} + return html`${this._urls?.map((url) => this._renderItem(url))} Add`; } - private _renderItem(item: FolderTreeItemModel) { - // TODO: remove when we have a way to handle trashed items - const tempItem = item as FolderTreeItemModel & { isTrashed: boolean }; + private _renderItem(url: Url) { + return html` + + + `; + // TODO: remove when we have a way to handle trashed items + //const tempItem = item as FolderTreeItemModel & { isTrashed: boolean }; + + /* return html` ${tempItem.isTrashed ? html` Trashed ` : nothing} `; + */ + } + private _renderItemIcon(url: Url) { + if (url.href === 'key') return html``; + if (url.target === true) return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts index 45783537fd..b733792ff9 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts @@ -327,7 +327,7 @@ export const data: Array = [ data: [ { alias: 'validationLimit', - value: { max: 1 }, + value: { max: 2 }, }, ], }, diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts index aafb22eb9e..39a56104f1 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts @@ -1,12 +1,17 @@ import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, state } from 'lit/decorators.js'; +import { customElement, query, state } from 'lit/decorators.js'; +import { UUIBooleanInputEvent, UUIInputElement, UUIToggleElement } from '@umbraco-ui/uui'; import { UmbModalLayoutElement } from '../modal-layout.element'; export interface UmbModalMultiUrlPickerData { - title?: string; + UrlString?: string; + anchorString?: string; + linkTitle?: string; + target?: boolean; hideAnchor?: boolean; - selection?: string; + treeItem?: string; + ignoreUserStartNodes?: boolean; } import { UmbTreeElement } from '../../../../backoffice/shared/components/tree/tree.element'; @@ -33,11 +38,13 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement - + Link Title - + Target - Open the link in a new tab + Open the link in a new tab
    @@ -112,13 +141,27 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement - `; - } else { - return html``; - } + return html` + Link + + `; + } + + private _renderAnchorInput() { + if (this._layout.hideAnchor) return; + return html` + Anchor / querystring + + `; } private _renderTrees() { @@ -127,7 +170,7 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement
    @@ -137,7 +180,7 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement`; } } From c9e27de687c94bd8bbe495848f4eb6d332f9850e Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Thu, 16 Feb 2023 13:09:32 +0100 Subject: [PATCH 025/116] linkpicker --- .../input-multi-url-picker.element.ts | 140 ++++++++---------- ...property-editor-ui-color-picker.element.ts | 1 - ...erty-editor-ui-multi-url-picker.element.ts | 11 +- .../src/core/mocks/data/data-type.data.ts | 19 ++- .../modal-layout-link-picker.element.ts} | 120 +++++++++------ .../modal-layout-multi-url-picker.stories.ts | 4 +- .../src/core/modal/modal.service.ts | 12 +- 7 files changed, 161 insertions(+), 146 deletions(-) rename src/Umbraco.Web.UI.Client/src/core/modal/layouts/{multi-url-picker/modal-layout-multi-url-picker.element.ts => link-picker/modal-layout-link-picker.element.ts} (56%) rename src/Umbraco.Web.UI.Client/src/core/modal/layouts/{multi-url-picker => link-picker}/modal-layout-multi-url-picker.stories.ts (89%) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts index 38bb0cb82d..6f8be61e30 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts @@ -4,14 +4,19 @@ import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { UmbLitElement } from '@umbraco-cms/element'; -import { DocumentTreeItemModel, FolderTreeItemModel } from '@umbraco-cms/backend-api'; +import { DocumentTreeItemModel, EntityTreeItemModel, FolderTreeItemModel } from '@umbraco-cms/backend-api'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; +import { UmbObserverController } from '@umbraco-cms/observable-api'; -export type OverlaySize = 'small' | 'medium' | 'large'; -export interface Url { - title: string; - href: string; - target: boolean; +export interface Link { + icon?: string; + name?: string; + published?: boolean; + queryString?: string; + target?: string; + trashed?: boolean; + udi?: string; + url?: string; } @customElement('umb-input-multi-url-picker') @@ -70,108 +75,81 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen @property({ type: Boolean, attribute: 'hide-anchor' }) hideAnchor?: boolean; + @property() + ignoreUserStartNodes?: boolean; + /** * @type {"small" | "medium" | "large"} * @attr * @default "small" */ @property() - overlaySize: OverlaySize = 'small'; - - // TODO: do we need both selectedKeys and value? If we just use value we follow the same pattern as native form controls. - private _selectedKeys: Array = []; - public get selectedKeys(): Array { - return this._selectedKeys; - } - public set selectedKeys(keys: Array) { - this._selectedKeys = keys; - super.value = keys.join(','); - } + overlaySize?: 'small' | 'medium' | 'large' | 'full'; @property() - public set value(keysString: string) { - if (keysString !== this._value) { - this.selectedKeys = keysString.split(/[ ,]+/); - } - } - - @property() - target = false; - - @property() - title = ''; - - @property() - url = ''; - - @state() - private _urls?: Url[] = [ - { - title: 'Cake', - href: 'google.com', - target: true, - }, - ]; - - @state() - private _items?: Array; + links: Array = []; private _modalService?: UmbModalService; + private _pickedItemsObserver?: UmbObserverController; constructor() { super(); - this.addValidator( - 'rangeUnderflow', - () => this.minMessage, - () => !!this.min && this._selectedKeys.length < this.min - ); - this.addValidator( - 'rangeOverflow', - () => this.maxMessage, - () => !!this.max && this._selectedKeys.length > this.max - ); - this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { this._modalService = instance; }); } - private _openPicker() { - const modalHandler = this._modalService?.multiUrlPicker({ treeItem: this._selectedKeys[0], target: this.target }); - modalHandler?.onClose().then(({ selection }: any) => { - this.selectedKeys[0] = selection.treeItem; - this.target = selection.target; - this.url = selection.href; + private async _observePickedDocumentsOrMedias() { + this._pickedItemsObserver?.destroy(); + } + + private _removeItem(index: number) { + this.links.splice(index, 1); + this.requestUpdate(); + } + + private _openPicker(data?: Link, index?: number) { + const modalHandler = this._modalService?.linkPicker( + { + name: data?.name || undefined, + published: data?.published || undefined, + queryString: data?.queryString || undefined, + target: data?.target || undefined, + trashed: data?.trashed || undefined, + udi: data?.udi || undefined, + url: data?.url || undefined, + }, + { + hideAnchor: this.hideAnchor, + ignoreUserStartNodes: this.ignoreUserStartNodes, + overlaySize: this.overlaySize || 'small', + } + ); + modalHandler?.onClose().then((newUrl: Link) => { + if (!newUrl) return; + + if (index !== undefined && index >= 0) this.links[index] = newUrl; + else this.links.push(newUrl); + this.requestUpdate(); }); } render() { - return html`${this._urls?.map((url) => this._renderItem(url))} + return html`${this.links?.map((link, index) => this._renderItem(link, index))} Add`; } - private _renderItem(url: Url) { - return html` - - + private _renderItem(link: Link, index: number) { + return html` + + + Remove + `; - - // TODO: remove when we have a way to handle trashed items - //const tempItem = item as FolderTreeItemModel & { isTrashed: boolean }; - - /* - return html` - - ${tempItem.isTrashed ? html` Trashed ` : nothing} - - - `; - */ - } - private _renderItemIcon(url: Url) { - if (url.href === 'key') return html``; - if (url.target === true) return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts index ce6b39d155..453b358ee0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts @@ -2,7 +2,6 @@ import { html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { UUIColorSwatchesEvent } from '@umbraco-ui/uui'; -import '../../../../shared/components/color-picker/color-picker.element'; import { UmbLitElement } from '@umbraco-cms/element'; import type { DataTypePropertyModel } from '@umbraco-cms/backend-api'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts index a6d394ba46..0bf35f6cee 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts @@ -3,9 +3,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { UmbInputMultiUrlPickerElement } from '../../../components/input-multi-url-picker/input-multi-url-picker.element'; -import type { OverlaySize } from '../../../components/input-multi-url-picker/input-multi-url-picker.element'; import { UmbLitElement } from '@umbraco-cms/element'; -import type { DataTypePropertyData } from '@umbraco-cms/models'; /** * @element umb-property-editor-ui-multi-url-picker @@ -22,7 +20,7 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement { public set value(value: string[]) { this._value = value || []; } - + /* @property({ type: Array, attribute: false }) public set config(config: DataTypePropertyData[]) { const overlaySize = config.find((x) => x.alias === 'overlaySize'); @@ -30,22 +28,19 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement { const hideAnchor = config.find((x) => x.alias === 'hideAnchor'); if (hideAnchor) this._hideAnchor = hideAnchor.value; - } + }*/ - @state() - private _overlaySize?: OverlaySize; @state() private _hideAnchor?: boolean; private _onChange(event: CustomEvent) { - this._value = (event.target as UmbInputMultiUrlPickerElement).selectedKeys; + //this._value = (event.target as UmbInputMultiUrlPickerElement); this.dispatchEvent(new CustomEvent('property-value-change')); } render() { return html``; } diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts index 1fb6462337..7cd42af884 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts @@ -119,7 +119,24 @@ export const data: Array = [ parentKey: null, propertyEditorAlias: 'Umbraco.MultiUrlPicker', propertyEditorUiAlias: 'Umb.PropertyEditorUI.MultiUrlPicker', - data: [], + data: [ + { + alias: 'hideAnchor', + value: false, + }, + { + alias: 'ignoreUserStartNodes', + value: false, + }, + { + alias: 'maxNumber', + value: null, + }, + { + alias: 'minNumber', + value: null, + }, + ], }, { type: 'data-type', diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/link-picker/modal-layout-link-picker.element.ts similarity index 56% rename from src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts rename to src/Umbraco.Web.UI.Client/src/core/modal/layouts/link-picker/modal-layout-link-picker.element.ts index 39a56104f1..972150db4f 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/link-picker/modal-layout-link-picker.element.ts @@ -1,23 +1,28 @@ -import { css, html } from 'lit'; +import { css, html, nothing } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, query, state } from 'lit/decorators.js'; -import { UUIBooleanInputEvent, UUIInputElement, UUIToggleElement } from '@umbraco-ui/uui'; +import { UUIBooleanInputEvent, UUIInputElement } from '@umbraco-ui/uui'; import { UmbModalLayoutElement } from '../modal-layout.element'; - -export interface UmbModalMultiUrlPickerData { - UrlString?: string; - anchorString?: string; - linkTitle?: string; - target?: boolean; - hideAnchor?: boolean; - treeItem?: string; - ignoreUserStartNodes?: boolean; -} - import { UmbTreeElement } from '../../../../backoffice/shared/components/tree/tree.element'; +export interface LinkPickerData { + icon?: string; + name?: string; + published?: boolean; + queryString?: string; + target?: string; + trashed?: boolean; + udi?: string; + url?: string; +} + +export interface LinkPickerConfig { + hideAnchor?: boolean; + ignoreUserStartNodes?: boolean; + overlaySize?: 'small' | 'medium' | 'large' | 'full'; +} @customElement('umb-modal-layout-multi-url-picker') -export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement { +export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement { static styles = [ UUITextStyles, css` @@ -53,16 +58,19 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement + @input=${() => (this._link.name = this._linkTitleInput.value as string)} + .value="${this._link.name ?? ''}"> Target Open the link in a new tab + label="Toggle if link should open in a new tab" + .checked="${this._link.target === '_blank' ? true : false}" + @change="${(e: UUIBooleanInputEvent) => + e.target.checked ? (this._link.target = '_blank') : (this._link.target = '')}"> + Open the link in a new tab +
    @@ -147,30 +167,32 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement + .value="${this._link.udi ?? this._link.url ?? ''}" + @input=${() => (this._link.url = this._linkInput.value as string)} + .disabled="${this._link.udi ? true : false}"> `; } private _renderAnchorInput() { - if (this._layout.hideAnchor) return; + if (this._layout.hideAnchor) return nothing; return html` Anchor / querystring + @input=${this._handleQueryString} + .value="${this._link.queryString ?? ''}"> `; } private _renderTrees() { return html`Link to page - +
    @@ -180,7 +202,7 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement`; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/link-picker/modal-layout-multi-url-picker.stories.ts similarity index 89% rename from src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.stories.ts rename to src/Umbraco.Web.UI.Client/src/core/modal/layouts/link-picker/modal-layout-multi-url-picker.stories.ts index 4bb37b0aaf..d41ed207e7 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/multi-url-picker/modal-layout-multi-url-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/link-picker/modal-layout-multi-url-picker.stories.ts @@ -1,5 +1,5 @@ import '../../../../backoffice/shared/components/body-layout/body-layout.element'; -import './modal-layout-multi-url-picker.element'; +import './modal-layout-link-picker.element'; import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit'; @@ -7,7 +7,7 @@ import { html } from 'lit'; import type { UmbModalLayoutMultiUrlPickerElement, UmbModalMultiUrlPickerData, -} from './modal-layout-multi-url-picker.element'; +} from './modal-layout-link-picker.element'; export default { title: 'API/Modals/Layouts/Multi Url Picker', diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts b/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts index 9d3debf9ef..21f790dcda 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts @@ -5,7 +5,7 @@ import './layouts/media-picker/modal-layout-media-picker.element'; import './layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element'; import './layouts/modal-layout-current-user.element'; import './layouts/icon-picker/modal-layout-icon-picker.element'; -import './layouts/multi-url-picker/modal-layout-multi-url-picker.element'; +import './layouts/link-picker/modal-layout-link-picker.element'; import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar'; import { BehaviorSubject } from 'rxjs'; @@ -15,7 +15,7 @@ import type { UmbModalConfirmData } from './layouts/confirm/modal-layout-confirm import type { UmbModalContentPickerData } from './layouts/content-picker/modal-layout-content-picker.element'; import type { UmbModalPropertyEditorUIPickerData } from './layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element'; import type { UmbModalMediaPickerData } from './layouts/media-picker/modal-layout-media-picker.element'; -import type { UmbModalMultiUrlPickerData } from './layouts/multi-url-picker/modal-layout-multi-url-picker.element'; +import type { LinkPickerConfig, LinkPickerData } from './layouts/link-picker/modal-layout-link-picker.element'; import { UmbModalHandler } from './modal-handler'; import { UmbContextToken } from '@umbraco-cms/context-api'; @@ -95,8 +95,12 @@ export class UmbModalService { * @return {*} {UmbModalHandler} * @memberof UmbModalService */ - public multiUrlPicker(data?: UmbModalMultiUrlPickerData): UmbModalHandler { - return this.open('umb-modal-layout-multi-url-picker', { data, type: 'sidebar', size: 'small' }); + public linkPicker(data?: LinkPickerData, config?: LinkPickerConfig): UmbModalHandler { + return this.open('umb-modal-layout-multi-url-picker', { + data, + type: 'sidebar', + size: config?.overlaySize || 'small', + }); } /** From ec7059ee263e681c0880085dc76e2baa534ff14f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 13:17:54 +0100 Subject: [PATCH 026/116] add dropdown element to supply styles to the uui popover element --- .../components/dropdown/dropdown.element.ts | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/components/dropdown/dropdown.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/dropdown/dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/dropdown/dropdown.element.ts new file mode 100644 index 0000000000..8f6ce7868a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/dropdown/dropdown.element.ts @@ -0,0 +1,55 @@ +import { UUITextStyles } from '@umbraco-ui/uui-css'; +import { css, html, nothing } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { UmbLitElement } from '@umbraco-cms/element'; + +@customElement('umb-dropdown') +export class UmbDropdownElement extends UmbLitElement { + static styles = [ + UUITextStyles, + css` + #container { + display: inline-block; + } + + #dropdown { + overflow: hidden; + z-index: -1; + background-color: var(--uui-combobox-popover-background-color, var(--uui-color-surface)); + border: 1px solid var(--uui-color-border); + border-radius: var(--uui-border-radius); + width: 100%; + height: 100%; + box-sizing: border-box; + box-shadow: var(--uui-shadow-depth-3); + width: 500px; + } + `, + ]; + + @property({ type: Boolean, reflect: true }) + open = false; + + render() { + return html` + + + ${this.open ? this.#renderDropdown() : nothing} + + `; + } + + #renderDropdown() { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-dropdown': UmbDropdownElement; + } +} From 4d951f75c1b7e98395718784a3b57a804df11546 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 13:18:03 +0100 Subject: [PATCH 027/116] import dropdown element --- .../src/backoffice/shared/components/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts index 2d6e7725aa..565fd6e060 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts @@ -8,6 +8,7 @@ import './ref-property-editor-ui/ref-property-editor-ui.element'; import './content-property/content-property.element'; import './table/table.element'; import './code-block/code-block.element'; +import './dropdown/dropdown.element'; import './extension-slot/extension-slot.element'; import './workspace/workspace-layout/workspace-layout.element'; From 4c8cb817961c30371f703a6b1016d0474bef40b0 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 13:18:30 +0100 Subject: [PATCH 028/116] use requestItems in delete action instead of treeItems --- .../backoffice/shared/entity-actions/delete/delete.action.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/entity-actions/delete/delete.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/entity-actions/delete/delete.action.ts index 58e9d919ae..3521026991 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/entity-actions/delete/delete.action.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/entity-actions/delete/delete.action.ts @@ -4,7 +4,7 @@ import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; export class UmbDeleteEntityAction< - T extends { delete(unique: string): Promise; requestTreeItems(uniques: Array): any } + T extends { delete(unique: string): Promise; requestItems(uniques: Array): any } > extends UmbEntityActionBase { #modalService?: UmbModalService; @@ -19,7 +19,7 @@ export class UmbDeleteEntityAction< async execute() { if (!this.repository || !this.#modalService) return; - const { data } = await this.repository.requestTreeItems([this.unique]); + const { data } = await this.repository.requestItems([this.unique]); if (data) { const item = data[0]; From 70fe4a6bc4bde1d97133cca9e9e4963d71d8fba1 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 13:18:46 +0100 Subject: [PATCH 029/116] add method to language repo to request items --- .../languages/repository/language.repository.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts index 252b7bb753..814f361eea 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts @@ -53,6 +53,8 @@ export class UmbLanguageRepository { return this.#detailDataSource.get(isoCode); } + // TODO: maybe this should be renamed to something more generic. + // Revisit when collection are in place async requestLanguages({ skip, take } = { skip: 0, take: 1000 }) { await this.#init; @@ -94,6 +96,19 @@ export class UmbLanguageRepository { return { data, error }; } + async requestItems(isoCode: Array) { + // HACK: filter client side until we have a proper server side endpoint + const { data, error } = await this.requestLanguages(); + + let items = undefined; + + if (data) { + items = data.items = data.items.filter((x) => isoCode.includes(x.isoCode!)); + } + + return { data: items, error }; + } + async delete(key: string) { await this.#init; From 14dd992decad18f7150811379ac78462c63948f4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 13:18:57 +0100 Subject: [PATCH 030/116] add todo --- .../workspace/language-root/language-root-workspace.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts index 557c30e9b5..7f103017db 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts @@ -128,6 +128,7 @@ export class UmbLanguageRootWorkspaceElement extends UmbLitElement { color="default" href="section/settings/language/create/root">
    +
    From 4c7277ac9a721475d693ce03650dbd690f8e46d1 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 13:19:11 +0100 Subject: [PATCH 031/116] render entity actions in language table --- ...root-table-delete-column-layout.element.ts | 71 +++++++------------ 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-table-delete-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-table-delete-column-layout.element.ts index 6ed6993d44..d318a967fa 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-table-delete-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-table-delete-column-layout.element.ts @@ -1,68 +1,47 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; -import { - UmbLanguageStore, - UmbLanguageStoreItemType, - UMB_LANGUAGE_STORE_CONTEXT_TOKEN, -} from '../../repository/language.store'; -import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../../core/modal'; +import { customElement, property, state } from 'lit/decorators.js'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; import { UmbLitElement } from '@umbraco-cms/element'; +import { LanguageModel } from '@umbraco-cms/backend-api'; @customElement('umb-language-root-table-delete-column-layout') export class UmbLanguageRootTableDeleteColumnLayoutElement extends UmbLitElement { static styles = [UUITextStyles, css``]; @property({ attribute: false }) - value!: UmbLanguageStoreItemType; + value!: LanguageModel; - #languageStore?: UmbLanguageStore; - #modalService?: UmbModalService; + @state() + _isOpen = false; - constructor() { - super(); - this.consumeContext(UMB_LANGUAGE_STORE_CONTEXT_TOKEN, (instance) => { - this.#languageStore = instance; - }); - - this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { - this.#modalService = instance; - }); + #onActionExecuted() { + this._isOpen = false; } - #handleDelete(event: MouseEvent) { - event.stopImmediatePropagation(); - if (!this.#languageStore) return; + #onClick() { + this._isOpen = !this._isOpen; + } - const modalHandler = this.#modalService?.confirm({ - headline: 'Delete language', - content: html` -
    - This will delete language ${this.value.name}. -
    - Are you sure you want to delete? - `, - color: 'danger', - confirmLabel: 'Delete', - }); - - modalHandler?.onClose().then(({ confirmed }) => { - if (confirmed) { - this.#languageStore?.delete([this.value.isoCode!]); - } - }); + #onClose() { + this._isOpen = false; } render() { + // TODO: we need to use conditionals on each action here. But until we have that in place + // we'll just remove all actions on the default language. if (this.value.isDefault) return nothing; - return html``; + return html` + + + + + `; } } From 8180be224fefa0eda38e737f7c61f43866d46836 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 13:20:11 +0100 Subject: [PATCH 032/116] clean up table header --- .../language-root/language-root-workspace.element.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts index 7f103017db..aba3ffb008 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language-root/language-root-workspace.element.ts @@ -34,7 +34,7 @@ export class UmbLanguageRootWorkspaceElement extends UmbLitElement { @state() private _tableColumns: Array = [ { - name: 'Language', + name: 'Name', alias: 'languageName', elementName: 'umb-language-root-table-name-column-layout', }, @@ -43,15 +43,15 @@ export class UmbLanguageRootWorkspaceElement extends UmbLitElement { alias: 'isoCode', }, { - name: 'Default language', + name: 'Default', alias: 'defaultLanguage', }, { - name: 'Mandatory language', + name: 'Mandatory', alias: 'mandatoryLanguage', }, { - name: 'Fall back language', + name: 'Fallback', alias: 'fallBackLanguage', }, { From fd1e4e0aae555fe3a0024c2f762627d1d09eee71 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 13:35:31 +0100 Subject: [PATCH 033/116] add await to save action --- .../src/backoffice/shared/workspace-actions/save.action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts index 26476e76f1..7a4253c55d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts @@ -14,6 +14,6 @@ export class UmbSaveWorkspaceAction extends UmbWorkspaceAction Date: Thu, 16 Feb 2023 13:39:28 +0100 Subject: [PATCH 034/116] remove detail from method names --- .../repository/detail-repository.interface.ts | 6 ++--- .../repository/document-type.repository.ts | 6 ++--- .../repository/document.repository.ts | 6 ++--- .../workspace/document-workspace.context.ts | 6 ++--- .../repository/media-type.repository.ts | 26 +++++++++---------- .../workspace/media-type-workspace.context.ts | 6 ++--- .../media/repository/media.repository.ts | 6 ++--- .../workspace/media-workspace.context.ts | 6 ++--- .../repository/member-group.repository.ts | 16 ++++++------ .../member-group-workspace.context.ts | 6 ++--- .../repository/member-type.repository.ts | 12 ++++----- .../sources/member-type.detail.server.data.ts | 6 ++--- .../member-type-workspace.context.ts | 8 +++--- .../repository/data-type.repository.ts | 6 ++--- .../workspace/data-type-workspace.context.ts | 6 ++--- .../repository/language.repository.ts | 2 +- .../shared/workspace-actions/save.action.ts | 2 +- .../repository/template.repository.ts | 6 ++--- ...ashboard-translation-dictionary.element.ts | 2 +- .../entity-actions/create/create.action.ts | 2 +- .../repository/dictionary.repository.ts | 6 ++--- .../workspace/dictionary-workspace.context.ts | 10 +++---- 22 files changed, 79 insertions(+), 79 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts index ff6e58a689..ea842ef36f 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts @@ -1,7 +1,7 @@ import type { ProblemDetailsModel } from '@umbraco-cms/backend-api'; export interface UmbDetailRepository { - createDetailsScaffold(parentKey: string | null): Promise<{ + createScaffold(parentKey: string | null): Promise<{ data?: DetailType; error?: ProblemDetailsModel; }>; @@ -11,11 +11,11 @@ export interface UmbDetailRepository { error?: ProblemDetailsModel; }>; - createDetail(data: DetailType): Promise<{ + create(data: DetailType): Promise<{ error?: ProblemDetailsModel; }>; - saveDetail(data: DetailType): Promise<{ + save(data: DetailType): Promise<{ error?: ProblemDetailsModel; }>; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts index f296ca5bdb..085cd78f5e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts @@ -113,7 +113,7 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, UmbDetailRe // DETAILS: - async createDetailsScaffold(parentKey: string | null) { + async createScaffold(parentKey: string | null) { await this.#init; if (!parentKey) { @@ -148,7 +148,7 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, UmbDetailRe // Could potentially be general methods: - async createDetail(template: ItemType) { + async create(template: ItemType) { await this.#init; if (!template || !template.key) { @@ -170,7 +170,7 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, UmbDetailRe return { error }; } - async saveDetail(item: ItemType) { + async save(item: ItemType) { await this.#init; if (!item || !item.key) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts index 7c0149a51f..7e0188a602 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts @@ -113,7 +113,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi // DETAILS: - async createDetailsScaffold(parentKey: string | null) { + async createScaffold(parentKey: string | null) { await this.#init; if (!parentKey) { @@ -143,7 +143,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi // Could potentially be general methods: - async createDetail(item: ItemType) { + async create(item: ItemType) { await this.#init; if (!item || !item.key) { @@ -165,7 +165,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi return { error }; } - async saveDetail(item: ItemType) { + async save(item: ItemType) { await this.#init; if (!item || !item.key) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts index 9832192b15..27e03ce283 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts @@ -50,7 +50,7 @@ export class UmbDocumentWorkspaceContext } async createScaffold(parentKey: string | null) { - const { data } = await this.#documentRepository.createDetailsScaffold(parentKey); + const { data } = await this.#documentRepository.createScaffold(parentKey); if (!data) return; this.#isNew = true; this.#data.next(data); @@ -175,9 +175,9 @@ export class UmbDocumentWorkspaceContext async save() { if (!this.#data.value) return; if (this.#isNew) { - await this.#documentRepository.createDetail(this.#data.value); + await this.#documentRepository.create(this.#data.value); } else { - await this.#documentRepository.saveDetail(this.#data.value); + await this.#documentRepository.save(this.#data.value); } // If it went well, then its not new anymore?. this.#isNew = false; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts index 71c8a21df6..bb3e3ff038 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts @@ -1,13 +1,13 @@ -import { UmbMediaTypeTreeStore, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN } from "./media-type.tree.store"; -import { UmbMediaTypeDetailServerDataSource } from "./sources/media-type.detail.server.data"; -import { UmbMediaTypeDetailStore, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from "./media-type.detail.store"; -import { MediaTypeTreeServerDataSource } from "./sources/media-type.tree.server.data"; -import { ProblemDetailsModel } from "@umbraco-cms/backend-api"; -import { UmbContextConsumerController } from "@umbraco-cms/context-api"; -import { UmbControllerHostInterface } from "@umbraco-cms/controller"; -import type { MediaTypeDetails } from "@umbraco-cms/models"; -import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from "@umbraco-cms/notification"; -import { UmbTreeRepository, RepositoryTreeDataSource } from "@umbraco-cms/repository"; +import { UmbMediaTypeTreeStore, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN } from './media-type.tree.store'; +import { UmbMediaTypeDetailServerDataSource } from './sources/media-type.detail.server.data'; +import { UmbMediaTypeDetailStore, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from './media-type.detail.store'; +import { MediaTypeTreeServerDataSource } from './sources/media-type.tree.server.data'; +import { ProblemDetailsModel } from '@umbraco-cms/backend-api'; +import { UmbContextConsumerController } from '@umbraco-cms/context-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import type { MediaTypeDetails } from '@umbraco-cms/models'; +import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; +import { UmbTreeRepository, RepositoryTreeDataSource } from '@umbraco-cms/repository'; export class UmbMediaTypeRepository implements UmbTreeRepository { #init!: Promise; @@ -103,7 +103,7 @@ export class UmbMediaTypeRepository implements UmbTreeRepository { // DETAILS - async createDetailsScaffold() { + async createScaffold() { await this.#init; return this.#detailSource.createScaffold(); } @@ -130,7 +130,7 @@ export class UmbMediaTypeRepository implements UmbTreeRepository { return this.#detailSource.delete(key); } - async saveDetail(mediaType: MediaTypeDetails) { + async save(mediaType: MediaTypeDetails) { await this.#init; // TODO: should we show a notification if the media type is missing? @@ -157,7 +157,7 @@ export class UmbMediaTypeRepository implements UmbTreeRepository { return { error }; } - async createDetail(mediaType: MediaTypeDetails) { + async create(mediaType: MediaTypeDetails) { await this.#init; if (!mediaType.name) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts index 4433b62d75..8fb4f07372 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts @@ -51,16 +51,16 @@ export class UmbWorkspaceMediaTypeContext } async createScaffold() { - const { data } = await this.#repo.createDetailsScaffold(); + const { data } = await this.#repo.createScaffold(); if (!data) return; this.#data.next(data); } async save() { if (!this.#data.value) return; - this.#repo.saveDetail(this.#data.value); + this.#repo.save(this.#data.value); } - + public destroy(): void { this.#data.complete(); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.repository.ts index 365e728589..831af8b644 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.repository.ts @@ -111,7 +111,7 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor // DETAILS: - async createDetailsScaffold(parentKey: string | null) { + async createScaffold(parentKey: string | null) { await this.#init; if (!parentKey) { @@ -141,7 +141,7 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor // Could potentially be general methods: - async createDetail(template: ItemDetailType) { + async create(template: ItemDetailType) { await this.#init; if (!template || !template.key) { @@ -163,7 +163,7 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor return { error }; } - async saveDetail(document: ItemDetailType) { + async save(document: ItemDetailType) { await this.#init; if (!document || !document.key) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts index c287034bf1..5779af6937 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts @@ -60,7 +60,7 @@ export class UmbMediaWorkspaceContext } async createScaffold(parentKey: string | null) { - const { data } = await this.#detailRepository.createDetailsScaffold(parentKey); + const { data } = await this.#detailRepository.createScaffold(parentKey); if (!data) return; this.#isNew = true; this.#data.next(data); @@ -69,9 +69,9 @@ export class UmbMediaWorkspaceContext async save() { if (!this.#data.value) return; if (this.#isNew) { - await this.#detailRepository.createDetail(this.#data.value); + await this.#detailRepository.create(this.#data.value); } else { - await this.#detailRepository.saveDetail(this.#data.value); + await this.#detailRepository.save(this.#data.value); } // If it went well, then its not new anymore?. this.#isNew = false; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts index 0084c13140..7f273071e5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts @@ -39,7 +39,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { this.#notificationService = instance; - }); + }); } async requestRootTreeItems() { @@ -89,9 +89,9 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep // DETAIL - async createDetailsScaffold() { + async createScaffold() { await this.#init; - return this.#detailSource.createScaffold(); + return this.#detailSource.createScaffold(); } async requestByKey(key: string) { @@ -111,7 +111,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep return { data, error }; } - async createDetail(detail: MemberGroupDetails) { + async create(detail: MemberGroupDetails) { await this.#init; if (!detail.name) { @@ -129,9 +129,9 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep return { data, error }; } - async saveDetail(memberGroup: MemberGroupDetails) { + async save(memberGroup: MemberGroupDetails) { await this.#init; - + if (!memberGroup || !memberGroup.name) { const error: ProblemDetailsModel = { title: 'Member group is missing' }; return { error }; @@ -140,7 +140,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep const { error } = await this.#detailSource.update(memberGroup); if (!error) { - const notification = { data: { message: `Member group '${memberGroup.name} saved`}}; + const notification = { data: { message: `Member group '${memberGroup.name} saved` } }; this.#notificationService?.peek('positive', notification); } @@ -149,7 +149,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep return { error }; } - + async delete(key: string) { await this.#init; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts index 726841e634..f3b03c6436 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts @@ -52,16 +52,16 @@ export class UmbWorkspaceMemberGroupContext } async createScaffold() { - const { data } = await this.#repo.createDetailsScaffold(); + const { data } = await this.#repo.createScaffold(); if (!data) return; this.#data.next(data); } async save() { if (!this.#data.value) return; - this.#repo.saveDetail(this.#data.value); + this.#repo.save(this.#data.value); } - + public destroy(): void { this.#data.complete(); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts index d2512d0aa0..c03d4eeb44 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts @@ -106,9 +106,9 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo // DETAILS - async createDetailsScaffold() { + async createScaffold() { await this.#init; - return this.#detailSource.createDetailsScaffold(); + return this.#detailSource.createScaffold(); } async requestByKey(key: string) { @@ -153,7 +153,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo return { error }; } - async saveDetail(detail: ItemType) { + async save(detail: ItemType) { await this.#init; // TODO: should we show a notification if the MemberType is missing? @@ -163,7 +163,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo return { error }; } - const { error } = await this.#detailSource.saveDetail(detail); + const { error } = await this.#detailSource.save(detail); if (!error) { const notification = { data: { message: `Member type '${detail.name}' saved` } }; @@ -180,7 +180,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo return { error }; } - async createDetail(detail: MemberTypeDetails) { + async create(detail: MemberTypeDetails) { await this.#init; if (!detail.name) { @@ -188,7 +188,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo return { error }; } - const { data, error } = await this.#detailSource.createDetail(detail); + const { data, error } = await this.#detailSource.create(detail); if (!error) { const notification = { data: { message: `Member type '${detail.name}' created` } }; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.detail.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.detail.server.data.ts index 01f2f5537a..58ec90e115 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.detail.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.detail.server.data.ts @@ -22,7 +22,7 @@ export class UmbMemberTypeDetailServerDataSource implements UmbDetailRepository< * @return {*} * @memberof UmbMemberTypeDetailServerDataSource */ - async createDetailsScaffold() { + async createScaffold() { const data = {} as MemberTypeDetails; return { data }; } @@ -45,7 +45,7 @@ export class UmbMemberTypeDetailServerDataSource implements UmbDetailRepository< * @return {*} * @memberof UmbMemberTypeDetailServerDataSource */ - async saveDetail(memberType: MemberTypeDetails) { + async save(memberType: MemberTypeDetails) { if (!memberType.key) { const error: ProblemDetailsModel = { title: 'MemberType key is missing' }; return { error }; @@ -73,7 +73,7 @@ export class UmbMemberTypeDetailServerDataSource implements UmbDetailRepository< * @return {*} * @memberof UmbMemberTypeDetailServerDataSource */ - async createDetail(data: MemberTypeDetails) { + async create(data: MemberTypeDetails) { const requestBody = { name: data.name, }; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts index 5f076fa40b..ce8b93765e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts @@ -33,7 +33,7 @@ export class UmbWorkspaceMemberTypeContext } async createScaffold() { - const { data } = await this.#dataTypeRepository.createDetailsScaffold(); + const { data } = await this.#dataTypeRepository.createScaffold(); if (!data) return; this.#isNew = true; this.#data.next(data); @@ -42,7 +42,7 @@ export class UmbWorkspaceMemberTypeContext getData() { return this.#data.getValue(); } - + getEntityKey() { return this.getData()?.key || ''; } @@ -62,9 +62,9 @@ export class UmbWorkspaceMemberTypeContext async save() { if (!this.#data.value) return; if (this.#isNew) { - await this.#dataTypeRepository.createDetail(this.#data.value); + await this.#dataTypeRepository.create(this.#data.value); } else { - await this.#dataTypeRepository.saveDetail(this.#data.value); + await this.#dataTypeRepository.save(this.#data.value); } // If it went well, then its not new anymore?. this.#isNew = false; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts index 0b93de9956..0c52ff35ad 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts @@ -113,7 +113,7 @@ export class UmbDataTypeRepository implements UmbTreeRepository, UmbDetailReposi // DETAILS: - async createDetailsScaffold(parentKey: string | null) { + async createScaffold(parentKey: string | null) { await this.#init; if (!parentKey) { @@ -148,7 +148,7 @@ export class UmbDataTypeRepository implements UmbTreeRepository, UmbDetailReposi // Could potentially be general methods: - async createDetail(template: ItemType) { + async create(template: ItemType) { await this.#init; if (!template || !template.key) { @@ -170,7 +170,7 @@ export class UmbDataTypeRepository implements UmbTreeRepository, UmbDetailReposi return { error }; } - async saveDetail(item: ItemType) { + async save(item: ItemType) { await this.#init; if (!item || !item.key) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts index ace8a934a8..e235d4be01 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts @@ -34,7 +34,7 @@ export class UmbDataTypeWorkspaceContext } async createScaffold(parentKey: string | null) { - const { data } = await this.#dataTypeRepository.createDetailsScaffold(parentKey); + const { data } = await this.#dataTypeRepository.createScaffold(parentKey); if (!data) return; this.#isNew = true; this.#data.next(data); @@ -76,9 +76,9 @@ export class UmbDataTypeWorkspaceContext async save() { if (!this.#data.value) return; if (this.#isNew) { - await this.#dataTypeRepository.createDetail(this.#data.value); + await this.#dataTypeRepository.create(this.#data.value); } else { - await this.#dataTypeRepository.saveDetail(this.#data.value); + await this.#dataTypeRepository.save(this.#data.value); } // If it went well, then its not new anymore?. this.#isNew = false; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts index 814f361eea..1445ac732e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts @@ -68,7 +68,7 @@ export class UmbLanguageRepository { return { data, error, asObservable: () => this.#languageStore!.data }; } - async update(language: LanguageModel) { + async save(language: LanguageModel) { await this.#init; const { data, error } = await this.#detailDataSource.update(language); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts index 7a4253c55d..c850de8d92 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts @@ -14,6 +14,6 @@ export class UmbSaveWorkspaceAction extends UmbWorkspaceAction get location header to route to new item console.log(result); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts index 53b6d4117f..aeb8b448a2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts @@ -47,7 +47,7 @@ export default class UmbCreateDictionaryEntityAction extends UmbEntityActionBase const { name }: UmbCreateDictionaryModalResultData = await modalHandler.onClose(); if (!name) return; - const result = await this.repository?.createDetail({ name, parentKey: this.unique, translations: [], key: ''}); + const result = await this.repository?.create({ name, parentKey: this.unique, translations: [], key: '' }); // TODO => get location header to route to new item console.log(result); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts index dd9c5e23dd..0ed7a54092 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts @@ -103,7 +103,7 @@ export class UmbDictionaryRepository implements UmbTreeRepository, UmbDetailRepo // DETAILS - async createDetailsScaffold(parentKey: string | null) { + async createScaffold(parentKey: string | null) { await this.#init; if (!parentKey) { @@ -141,7 +141,7 @@ export class UmbDictionaryRepository implements UmbTreeRepository, UmbDetailRepo return this.#detailSource.delete(key); } - async saveDetail(dictionary: DictionaryDetails) { + async save(dictionary: DictionaryDetails) { await this.#init; // TODO: should we show a notification if the dictionary is missing? @@ -168,7 +168,7 @@ export class UmbDictionaryRepository implements UmbTreeRepository, UmbDetailRepo return { error }; } - async createDetail(detail: DictionaryDetails) { + async create(detail: DictionaryDetails) { await this.#init; if (!detail.name) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts index 723af850f5..5081fbaf3a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts @@ -47,7 +47,7 @@ export class UmbWorkspaceDictionaryContext const updatedValue = this.#data.value.translations?.map((translationItem) => { if (translationItem.isoCode === isoCode) { - return { ...translationItem, translation}; + return { ...translationItem, translation }; } return translationItem; }) ?? []; @@ -57,7 +57,7 @@ export class UmbWorkspaceDictionaryContext updatedValue?.push({ isoCode, translation }); } - this.#data.next({ ...this.#data.value, translations: updatedValue }); + this.#data.next({ ...this.#data.value, translations: updatedValue }); } async load(entityKey: string) { @@ -68,16 +68,16 @@ export class UmbWorkspaceDictionaryContext } async createScaffold(parentKey: string | null) { - const { data } = await this.#repo.createDetailsScaffold(parentKey); + const { data } = await this.#repo.createScaffold(parentKey); if (!data) return; this.#data.next(data); } async save() { if (!this.#data.value) return; - this.#repo.saveDetail(this.#data.value); + this.#repo.save(this.#data.value); } - + public destroy(): void { this.#data.complete(); } From d083b63bd72b4772f12434cd32cef6b3875ea1b0 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Thu, 16 Feb 2023 14:00:14 +0100 Subject: [PATCH 035/116] configs --- .../input-multi-url-picker.element.ts | 28 ++++++++------- ...erty-editor-ui-multi-url-picker.element.ts | 34 ++++++++++++++++--- .../src/core/mocks/data/data-type.data.ts | 8 +++-- .../modal-layout-link-picker.element.ts | 16 +++++---- .../modal-layout-multi-url-picker.stories.ts | 20 +++++------ .../src/core/modal/modal.service.ts | 13 ++++--- 6 files changed, 78 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts index 6f8be61e30..4bac03b304 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts @@ -1,10 +1,9 @@ -import { css, html, nothing } from 'lit'; +import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, property, state } from 'lit/decorators.js'; -import { ifDefined } from 'lit-html/directives/if-defined.js'; +import { customElement, property } from 'lit/decorators.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { UmbLitElement } from '@umbraco-cms/element'; -import { DocumentTreeItemModel, EntityTreeItemModel, FolderTreeItemModel } from '@umbraco-cms/backend-api'; +import { FolderTreeItemModel } from '@umbraco-cms/backend-api'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; import { UmbObserverController } from '@umbraco-cms/observable-api'; @@ -90,20 +89,25 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen links: Array = []; private _modalService?: UmbModalService; - private _pickedItemsObserver?: UmbObserverController; constructor() { super(); + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this.links.length < this.min + ); + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this.links.length > this.max + ); this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { this._modalService = instance; }); } - private async _observePickedDocumentsOrMedias() { - this._pickedItemsObserver?.destroy(); - } - private _removeItem(index: number) { this.links.splice(index, 1); this.requestUpdate(); @@ -141,12 +145,10 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen } private _renderItem(link: Link, index: number) { - return html` + return html` + Edit Remove `; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts index 0bf35f6cee..a360d8c23b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts @@ -4,10 +4,12 @@ import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { UmbInputMultiUrlPickerElement } from '../../../components/input-multi-url-picker/input-multi-url-picker.element'; import { UmbLitElement } from '@umbraco-cms/element'; +import { DataTypePropertyModel } from '@umbraco-cms/backend-api'; /** * @element umb-property-editor-ui-multi-url-picker */ + @customElement('umb-property-editor-ui-multi-url-picker') export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement { static styles = [UUITextStyles]; @@ -20,19 +22,39 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement { public set value(value: string[]) { this._value = value || []; } - /* + @property({ type: Array, attribute: false }) - public set config(config: DataTypePropertyData[]) { + public set config(config: DataTypePropertyModel[]) { const overlaySize = config.find((x) => x.alias === 'overlaySize'); - if (overlaySize) this._overlaySize = overlaySize.value as OverlaySize; + if (overlaySize) this._overlaySize = overlaySize.value; const hideAnchor = config.find((x) => x.alias === 'hideAnchor'); if (hideAnchor) this._hideAnchor = hideAnchor.value; - }*/ + + const ignoreUserStartNodes = config.find((x) => x.alias === 'ignoreUserStartNodes'); + if (ignoreUserStartNodes) this._ignoreUserStartNodes = ignoreUserStartNodes.value; + + const maxNumber = config.find((x) => x.alias === 'maxNumber'); + if (maxNumber) this._maxNumber = maxNumber.value; + + const minNumber = config.find((x) => x.alias === 'minNumber'); + if (minNumber) this._minNumber = minNumber.value; + } + @state() + private _overlaySize?: 'small' | 'medium' | 'large' | 'full'; @state() private _hideAnchor?: boolean; + @state() + private _ignoreUserStartNodes?: boolean; + + @state() + private _maxNumber?: number; + + @state() + private _minNumber?: number; + private _onChange(event: CustomEvent) { //this._value = (event.target as UmbInputMultiUrlPickerElement); this.dispatchEvent(new CustomEvent('property-value-change')); @@ -41,7 +63,11 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement { render() { return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts index 7cd42af884..13397b5334 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts @@ -120,6 +120,10 @@ export const data: Array = [ propertyEditorAlias: 'Umbraco.MultiUrlPicker', propertyEditorUiAlias: 'Umb.PropertyEditorUI.MultiUrlPicker', data: [ + { + alias: 'overlaySize', + value: 'small', + }, { alias: 'hideAnchor', value: false, @@ -130,11 +134,11 @@ export const data: Array = [ }, { alias: 'maxNumber', - value: null, + value: 2, }, { alias: 'minNumber', - value: null, + value: 0, }, ], }, diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/link-picker/modal-layout-link-picker.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/link-picker/modal-layout-link-picker.element.ts index 972150db4f..8bbeee8016 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/link-picker/modal-layout-link-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/link-picker/modal-layout-link-picker.element.ts @@ -5,7 +5,7 @@ import { UUIBooleanInputEvent, UUIInputElement } from '@umbraco-ui/uui'; import { UmbModalLayoutElement } from '../modal-layout.element'; import { UmbTreeElement } from '../../../../backoffice/shared/components/tree/tree.element'; -export interface LinkPickerData { +export interface UmbModalLinkPickerData { icon?: string; name?: string; published?: boolean; @@ -16,13 +16,15 @@ export interface LinkPickerData { url?: string; } -export interface LinkPickerConfig { +export interface UmbModalLinkPickerConfig { hideAnchor?: boolean; ignoreUserStartNodes?: boolean; overlaySize?: 'small' | 'medium' | 'large' | 'full'; } -@customElement('umb-modal-layout-multi-url-picker') -export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement { +@customElement('umb-modal-layout-link-picker') +export class UmbModalLayoutLinkPickerElement extends UmbModalLayoutElement< + UmbModalLinkPickerData & UmbModalLinkPickerConfig +> { static styles = [ UUITextStyles, css` @@ -58,7 +60,7 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement = () => html` +export const Overview: Story = () => html` - + `; diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts b/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts index 21f790dcda..9f2aaec0e3 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts @@ -15,7 +15,10 @@ import type { UmbModalConfirmData } from './layouts/confirm/modal-layout-confirm import type { UmbModalContentPickerData } from './layouts/content-picker/modal-layout-content-picker.element'; import type { UmbModalPropertyEditorUIPickerData } from './layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element'; import type { UmbModalMediaPickerData } from './layouts/media-picker/modal-layout-media-picker.element'; -import type { LinkPickerConfig, LinkPickerData } from './layouts/link-picker/modal-layout-link-picker.element'; +import type { + UmbModalLinkPickerConfig, + UmbModalLinkPickerData, +} from './layouts/link-picker/modal-layout-link-picker.element'; import { UmbModalHandler } from './modal-handler'; import { UmbContextToken } from '@umbraco-cms/context-api'; @@ -89,14 +92,14 @@ export class UmbModalService { } /** - * Opens an Multi URL Picker sidebar modal + * Opens an Link Picker sidebar modal * @public - * @param {UmbModalMultiUrlPickerData} [data] + * @param {(LinkPickerData & LinkPickerConfig)} [data] * @return {*} {UmbModalHandler} * @memberof UmbModalService */ - public linkPicker(data?: LinkPickerData, config?: LinkPickerConfig): UmbModalHandler { - return this.open('umb-modal-layout-multi-url-picker', { + public linkPicker(data?: UmbModalLinkPickerData, config?: UmbModalLinkPickerConfig): UmbModalHandler { + return this.open('umb-modal-layout-link-picker', { data, type: 'sidebar', size: config?.overlaySize || 'small', From e13fa55ada2748a48f1da5b609c3d7154a97f487 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Thu, 16 Feb 2023 14:12:57 +0100 Subject: [PATCH 036/116] validation? --- .../input-multi-url-picker.element.ts | 24 +++++++++---------- ...erty-editor-ui-multi-url-picker.element.ts | 20 +++++++--------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts index 4bac03b304..bc65dcb756 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts @@ -3,11 +3,9 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property } from 'lit/decorators.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { UmbLitElement } from '@umbraco-cms/element'; -import { FolderTreeItemModel } from '@umbraco-cms/backend-api'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; -import { UmbObserverController } from '@umbraco-cms/observable-api'; -export interface Link { +export interface MultiUrlData { icon?: string; name?: string; published?: boolean; @@ -86,7 +84,7 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen overlaySize?: 'small' | 'medium' | 'large' | 'full'; @property() - links: Array = []; + multiUrls: Array = []; private _modalService?: UmbModalService; @@ -95,12 +93,12 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen this.addValidator( 'rangeUnderflow', () => this.minMessage, - () => !!this.min && this.links.length < this.min + () => !!this.min && this.multiUrls.length < this.min ); this.addValidator( 'rangeOverflow', () => this.maxMessage, - () => !!this.max && this.links.length > this.max + () => !!this.max && this.multiUrls.length > this.max ); this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { @@ -109,11 +107,11 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen } private _removeItem(index: number) { - this.links.splice(index, 1); + this.multiUrls.splice(index, 1); this.requestUpdate(); } - private _openPicker(data?: Link, index?: number) { + private _openPicker(data?: MultiUrlData, index?: number) { const modalHandler = this._modalService?.linkPicker( { name: data?.name || undefined, @@ -130,21 +128,21 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen overlaySize: this.overlaySize || 'small', } ); - modalHandler?.onClose().then((newUrl: Link) => { + modalHandler?.onClose().then((newUrl: MultiUrlData) => { if (!newUrl) return; - if (index !== undefined && index >= 0) this.links[index] = newUrl; - else this.links.push(newUrl); + if (index !== undefined && index >= 0) this.multiUrls[index] = newUrl; + else this.multiUrls.push(newUrl); this.requestUpdate(); }); } render() { - return html`${this.links?.map((link, index) => this._renderItem(link, index))} + return html`${this.multiUrls?.map((link, index) => this._renderItem(link, index))} Add`; } - private _renderItem(link: Link, index: number) { + private _renderItem(link: MultiUrlData, index: number) { return html` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts index a360d8c23b..41163b19bb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts @@ -1,8 +1,10 @@ import { html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; -import { ifDefined } from 'lit-html/directives/if-defined.js'; -import { UmbInputMultiUrlPickerElement } from '../../../components/input-multi-url-picker/input-multi-url-picker.element'; +import { + UmbInputMultiUrlPickerElement, + MultiUrlData, +} from '../../../../shared/components/input-multi-url-picker/input-multi-url-picker.element'; import { UmbLitElement } from '@umbraco-cms/element'; import { DataTypePropertyModel } from '@umbraco-cms/backend-api'; @@ -14,14 +16,8 @@ import { DataTypePropertyModel } from '@umbraco-cms/backend-api'; export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement { static styles = [UUITextStyles]; - private _value: string[] = []; - @property({ type: Array }) - public get value(): string[] { - return this._value; - } - public set value(value: string[]) { - this._value = value || []; - } + @property() + value: MultiUrlData[] = []; @property({ type: Array, attribute: false }) public set config(config: DataTypePropertyModel[]) { @@ -56,7 +52,7 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement { private _minNumber?: number; private _onChange(event: CustomEvent) { - //this._value = (event.target as UmbInputMultiUrlPickerElement); + //TODO: Do something about the values this.dispatchEvent(new CustomEvent('property-value-change')); } @@ -68,7 +64,7 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement { .ignoreUserStartNodes=${this._ignoreUserStartNodes} .max=${this._maxNumber} .min=${this._minNumber} - .selectedKeys="${this._value}">`; + .links="${this.value}">`; } } From 7d3716b58eb0c23eaea21c236e90447298ac5f7e Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 14:13:08 +0100 Subject: [PATCH 037/116] let save action save or create --- .../backoffice/shared/workspace-actions/save.action.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts index c850de8d92..b42c834fe1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts @@ -14,6 +14,12 @@ export class UmbSaveWorkspaceAction extends UmbWorkspaceAction Date: Thu, 16 Feb 2023 14:13:36 +0100 Subject: [PATCH 038/116] remove save and delete from template workspace --- .../templates/workspace/template-workspace.context.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/workspace/template-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/workspace/template-workspace.context.ts index 1a2a970819..72d3917e63 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/workspace/template-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/workspace/template-workspace.context.ts @@ -43,13 +43,4 @@ export class UmbTemplateWorkspaceContext { if (!data) return; this.#data.next(data); } - - async save(isNew: boolean) { - if (!this.#data.value) return; - isNew ? this.#templateDetailRepo.insert(this.#data.value) : this.#templateDetailRepo.update(this.#data.value); - } - - async delete(key: string) { - await this.#templateDetailRepo.delete(key); - } } From bb747709be4f2a71cff5aa2c6dc622d29e6e0f5d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 14:13:52 +0100 Subject: [PATCH 039/116] add isNew to workspace interface --- .../workspace-context/workspace-context.interface.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts index 65dfa62416..219aab4e2b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts @@ -1,10 +1,8 @@ export interface UmbWorkspaceContextInterface { //readonly data: Observable; //getUnique(): string | undefined; - + isNew: boolean; getEntityType(): string; - getData(): T; - destroy(): void; } From a4f6eccac471c307d6ca6e925e8f229bb48db422 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 14:14:09 +0100 Subject: [PATCH 040/116] add isNew to template workspace --- .../templates/workspace/template-workspace.context.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/workspace/template-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/workspace/template-workspace.context.ts index 72d3917e63..fdea93d4a0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/workspace/template-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/workspace/template-workspace.context.ts @@ -13,6 +13,8 @@ export class UmbTemplateWorkspaceContext { name = createObservablePart(this.#data, (data) => data?.name); content = createObservablePart(this.#data, (data) => data?.content); + isNew = false; + constructor(host: UmbControllerHostInterface) { this.#host = host; this.#templateDetailRepo = new UmbTemplateDetailRepository(this.#host); @@ -39,6 +41,7 @@ export class UmbTemplateWorkspaceContext { } async createScaffold(parentKey: string | null) { + this.isNew = true; const { data } = await this.#templateDetailRepo.createScaffold(parentKey); if (!data) return; this.#data.next(data); From 378191bbca25d0e5cb040bc4b400f6d226d6ff8f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 14:14:29 +0100 Subject: [PATCH 041/116] clean up --- .../repository/language.repository.ts | 47 ++++++++++--------- .../language/language-workspace.context.ts | 2 +- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts index 1445ac732e..ea08768132 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts @@ -32,16 +32,7 @@ export class UmbLanguageRepository { ]); } - /** - * Creates a new Language scaffold - * @param - * @return {*} - * @memberof UmbLanguageServerDataSource - */ - async createDetailsScaffold() { - return this.#detailDataSource.createScaffold(); - } - + // TODO: maybe this should be renamed to something more generic? async requestByIsoCode(isoCode: string) { await this.#init; @@ -68,18 +59,27 @@ export class UmbLanguageRepository { return { data, error, asObservable: () => this.#languageStore!.data }; } - async save(language: LanguageModel) { - await this.#init; + async requestItems(isoCode: Array) { + // HACK: filter client side until we have a proper server side endpoint + const { data, error } = await this.requestLanguages(); - const { data, error } = await this.#detailDataSource.update(language); + let items = undefined; if (data) { - const notification = { data: { message: `Language saved` } }; - this.#notificationService?.peek('positive', notification); - this.#languageStore?.append(data); + items = data.items = data.items.filter((x) => isoCode.includes(x.isoCode!)); } - return { data, error }; + return { data: items, error }; + } + + /** + * Creates a new Language scaffold + * @param + * @return {*} + * @memberof UmbLanguageServerDataSource + */ + async createScaffold() { + return this.#detailDataSource.createScaffold(); } async create(language: LanguageModel) { @@ -96,17 +96,18 @@ export class UmbLanguageRepository { return { data, error }; } - async requestItems(isoCode: Array) { - // HACK: filter client side until we have a proper server side endpoint - const { data, error } = await this.requestLanguages(); + async save(language: LanguageModel) { + await this.#init; - let items = undefined; + const { data, error } = await this.#detailDataSource.update(language); if (data) { - items = data.items = data.items.filter((x) => isoCode.includes(x.isoCode!)); + const notification = { data: { message: `Language saved` } }; + this.#notificationService?.peek('positive', notification); + this.#languageStore?.append(data); } - return { data: items, error }; + return { data, error }; } async delete(key: string) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts index 9182536bcd..0e21d70a1e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts @@ -27,7 +27,7 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { } async createScaffold() { - const { data } = await this.#languageRepository.createDetailsScaffold(); + const { data } = await this.#languageRepository.createScaffold(); if (!data) return; this.isNew = true; this.#data.update(data); From 858975e21a86756d2c866beb80f7fc3ab9cefaae Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 14:19:12 +0100 Subject: [PATCH 042/116] fix type errors --- .../workspace/document-type-workspace.context.ts | 1 + .../members/workspace/member-workspace.context.ts | 3 ++- .../workspace/dictionary-workspace.context.ts | 1 + .../users/workspace/user-workspace.context.ts | 9 ++++++--- .../src/core/mocks/data/languages.data.ts | 14 +++++++------- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts index da36b169cb..efe6f99b1d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts @@ -8,6 +8,7 @@ export class UmbWorkspaceDocumentTypeContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { + isNew = false; #manager = new UmbEntityWorkspaceManager(this._host, 'document-type', UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN); public readonly data = this.#manager.state.asObservable(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts index d010b9edd0..0643082bba 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts @@ -8,6 +8,7 @@ export class UmbWorkspaceMemberContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { + isNew = false; #manager = new UmbEntityWorkspaceManager(this._host, 'member', UMB_MEMBER_DETAIL_STORE_CONTEXT_TOKEN); public readonly data = this.#manager.state.asObservable(); @@ -18,7 +19,7 @@ export class UmbWorkspaceMemberContext } setName(name: string) { - this.#manager.state.update({name}); + this.#manager.state.update({ name }); } getEntityType = this.#manager.getEntityType; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts index 5081fbaf3a..c926b618dc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts @@ -10,6 +10,7 @@ export class UmbWorkspaceDictionaryContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { + isNew = false; #host: UmbControllerHostInterface; #repo: UmbDictionaryRepository; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts index eb22cff826..7f1dd39cb5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts @@ -4,8 +4,11 @@ import { UmbWorkspaceEntityContextInterface } from '../../../shared/components/w import { UmbEntityWorkspaceManager } from '../../../shared/components/workspace/workspace-context/entity-manager-controller'; import type { UserDetails } from '@umbraco-cms/models'; -export class UmbWorkspaceUserContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - +export class UmbWorkspaceUserContext + extends UmbWorkspaceContext + implements UmbWorkspaceEntityContextInterface +{ + isNew = false; #manager = new UmbEntityWorkspaceManager(this._host, 'user', UMB_USER_STORE_CONTEXT_TOKEN); public readonly data = this.#manager.state.asObservable(); @@ -15,7 +18,7 @@ export class UmbWorkspaceUserContext extends UmbWorkspaceContext implements UmbW update = this.#manager.state.update; setName(name: string) { - this.#manager.state.update({name: name}) + this.#manager.state.update({ name: name }); } getEntityType = this.#manager.getEntityType; getUnique = this.#manager.getEntityKey; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts index 2b1f16d2b1..eff6787714 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts @@ -1,14 +1,14 @@ -import { UmbLanguageStoreItemType } from '../../../backoffice/settings/languages/repository/language.store'; import { UmbData } from './data'; +import { LanguageModel } from '@umbraco-cms/backend-api'; // Temp mocked database -class UmbLanguagesData extends UmbData { - constructor(data: UmbLanguageStoreItemType[]) { +class UmbLanguagesData extends UmbData { + constructor(data: LanguageModel[]) { super(data); } // skip can be number or null - getAll(skip = 0, take = this.data.length): Array { + getAll(skip = 0, take = this.data.length): Array { return this.data.slice(skip, take); } @@ -16,7 +16,7 @@ class UmbLanguagesData extends UmbData { return this.data.find((item) => item.isoCode === key); } - save(saveItems: Array) { + save(saveItems: Array) { saveItems.forEach((saveItem) => { const foundIndex = this.data.findIndex((item) => item.isoCode === saveItem.isoCode); if (foundIndex !== -1) { @@ -50,7 +50,7 @@ class UmbLanguagesData extends UmbData { return keys; } - updateData(updateItem: UmbLanguageStoreItemType) { + updateData(updateItem: LanguageModel) { const itemIndex = this.data.findIndex((item) => item.isoCode === updateItem.isoCode); const item = this.data[itemIndex]; if (!item) return; @@ -81,7 +81,7 @@ class UmbLanguagesData extends UmbData { } } -export const MockData: Array = [ +export const MockData: Array = [ { name: 'English', isoCode: 'en', From fb51ebf761e4b2f4e88a5341c0723e843c2715bf Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 14:29:37 +0100 Subject: [PATCH 043/116] add isNew to workspaces --- .../workspace/document-workspace.context.ts | 10 +++++----- .../workspace/media-type-workspace.context.ts | 1 + .../media/workspace/media-workspace.context.ts | 10 +++++----- .../workspace/member-type-workspace.context.ts | 10 +++++----- .../workspace/data-type-workspace.context.ts | 13 +++++++------ .../workspace/user-group-workspace.context.ts | 11 ++++++----- 6 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts index 27e03ce283..1ace3a7c48 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts @@ -19,7 +19,7 @@ export class UmbDocumentWorkspaceContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - #isNew = false; + isNew = false; #host: UmbControllerHostInterface; #documentRepository: UmbDocumentRepository; #documentTypeRepository: UmbDocumentTypeRepository; @@ -44,7 +44,7 @@ export class UmbDocumentWorkspaceContext async load(entityKey: string) { const { data } = await this.#documentRepository.requestByKey(entityKey); if (data) { - this.#isNew = false; + this.isNew = false; this.#data.next(data); } } @@ -52,7 +52,7 @@ export class UmbDocumentWorkspaceContext async createScaffold(parentKey: string | null) { const { data } = await this.#documentRepository.createScaffold(parentKey); if (!data) return; - this.#isNew = true; + this.isNew = true; this.#data.next(data); } @@ -174,13 +174,13 @@ export class UmbDocumentWorkspaceContext async save() { if (!this.#data.value) return; - if (this.#isNew) { + if (this.isNew) { await this.#documentRepository.create(this.#data.value); } else { await this.#documentRepository.save(this.#data.value); } // If it went well, then its not new anymore?. - this.#isNew = false; + this.isNew = false; } async delete(key: string) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts index 8fb4f07372..871b1ef489 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts @@ -10,6 +10,7 @@ export class UmbWorkspaceMediaTypeContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { + isNew = false; #host: UmbControllerHostInterface; #repo: UmbMediaTypeRepository; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts index 5779af6937..4905458014 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts @@ -10,7 +10,7 @@ export class UmbMediaWorkspaceContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - #isNew = false; + isNew = false; #host: UmbControllerHostInterface; #detailRepository: UmbMediaRepository; @@ -54,7 +54,7 @@ export class UmbMediaWorkspaceContext async load(entityKey: string) { const { data } = await this.#detailRepository.requestByKey(entityKey); if (data) { - this.#isNew = false; + this.isNew = false; this.#data.next(data); } } @@ -62,19 +62,19 @@ export class UmbMediaWorkspaceContext async createScaffold(parentKey: string | null) { const { data } = await this.#detailRepository.createScaffold(parentKey); if (!data) return; - this.#isNew = true; + this.isNew = true; this.#data.next(data); } async save() { if (!this.#data.value) return; - if (this.#isNew) { + if (this.isNew) { await this.#detailRepository.create(this.#data.value); } else { await this.#detailRepository.save(this.#data.value); } // If it went well, then its not new anymore?. - this.#isNew = false; + this.isNew = false; } async delete(key: string) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts index ce8b93765e..00e0ef78ea 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts @@ -11,7 +11,7 @@ export class UmbWorkspaceMemberTypeContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - #isNew = false; + isNew = false; #host: UmbControllerHostInterface; #dataTypeRepository: UmbMemberTypeRepository; @@ -27,7 +27,7 @@ export class UmbWorkspaceMemberTypeContext async load(entityKey: string) { const { data } = await this.#dataTypeRepository.requestByKey(entityKey); if (data) { - this.#isNew = false; + this.isNew = false; this.#data.next(data); } } @@ -35,7 +35,7 @@ export class UmbWorkspaceMemberTypeContext async createScaffold() { const { data } = await this.#dataTypeRepository.createScaffold(); if (!data) return; - this.#isNew = true; + this.isNew = true; this.#data.next(data); } @@ -61,13 +61,13 @@ export class UmbWorkspaceMemberTypeContext async save() { if (!this.#data.value) return; - if (this.#isNew) { + if (this.isNew) { await this.#dataTypeRepository.create(this.#data.value); } else { await this.#dataTypeRepository.save(this.#data.value); } // If it went well, then its not new anymore?. - this.#isNew = false; + this.isNew = false; } async delete(key: string) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts index e235d4be01..e8b10c2c24 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts @@ -1,4 +1,3 @@ -import { BehaviorSubject } from 'rxjs'; import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context'; import { UmbWorkspaceEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-entity-context.interface'; import { UmbDataTypeRepository } from '../repository/data-type.repository'; @@ -10,7 +9,7 @@ export class UmbDataTypeWorkspaceContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - #isNew = false; + isNew = false; #host: UmbControllerHostInterface; #dataTypeRepository: UmbDataTypeRepository; @@ -28,24 +27,26 @@ export class UmbDataTypeWorkspaceContext async load(key: string) { const { data } = await this.#dataTypeRepository.requestByKey(key); if (data) { - this.#isNew = false; + this.isNew = false; this.#data.update(data); } } async createScaffold(parentKey: string | null) { + this.isNew = true; const { data } = await this.#dataTypeRepository.createScaffold(parentKey); if (!data) return; - this.#isNew = true; this.#data.next(data); } getData() { return this.#data.getValue(); } + getEntityKey() { return this.getData()?.key || ''; } + getEntityType() { return 'data-type'; } @@ -75,13 +76,13 @@ export class UmbDataTypeWorkspaceContext async save() { if (!this.#data.value) return; - if (this.#isNew) { + if (this.isNew) { await this.#dataTypeRepository.create(this.#data.value); } else { await this.#dataTypeRepository.save(this.#data.value); } // If it went well, then its not new anymore?. - this.#isNew = false; + this.isNew = false; } async delete(key: string) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts index 470163bc3c..e7d36244a3 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts @@ -4,10 +4,11 @@ import { UmbWorkspaceEntityContextInterface } from '../../../shared/components/w import { UMB_USER_GROUP_STORE_CONTEXT_TOKEN } from '../user-group.store'; import type { UserGroupDetails } from '@umbraco-cms/models'; - -export class UmbWorkspaceUserGroupContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - - +export class UmbWorkspaceUserGroupContext + extends UmbWorkspaceContext + implements UmbWorkspaceEntityContextInterface +{ + isNew = false; #manager = new UmbEntityWorkspaceManager(this._host, 'user-group', UMB_USER_GROUP_STORE_CONTEXT_TOKEN); @@ -15,7 +16,7 @@ export class UmbWorkspaceUserGroupContext extends UmbWorkspaceContext implements public readonly name = this.#manager.state.getObservablePart((state) => state?.name); setName(name: string) { - this.#manager.state.update({name: name}) + this.#manager.state.update({ name: name }); } getEntityType = this.#manager.getEntityType; getUnique = this.#manager.getEntityKey; From 396020b1fe99761104d17a2504290297c2b279ae Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 14:34:39 +0100 Subject: [PATCH 044/116] add isNew --- .../member-groups/workspace/member-group-workspace.context.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts index f3b03c6436..335d2d8524 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts @@ -10,6 +10,7 @@ export class UmbWorkspaceMemberGroupContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { + isNew = false; #host: UmbControllerHostInterface; #repo: UmbMemberGroupRepository; From b6aeee25aa645d27506d01d7ed9f48f63170586a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 14:37:28 +0100 Subject: [PATCH 045/116] remove todo --- .../language/views/edit/edit-language-workspace-view.element.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 88281fa10b..113f50d818 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -111,7 +111,6 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { } } - // TODO: move some of these methods to the context #handleSearchChange(event: Event) { const target = event.composedPath()[0] as UUIComboboxElement; this._search = target.search; From c7e025e11520b74ea7751fa4110d04c2d7d324ac Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 14:43:41 +0100 Subject: [PATCH 046/116] remove space --- .../language/views/edit/edit-language-workspace-view.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 113f50d818..1671dddcd7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -226,7 +226,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { Date: Thu, 16 Feb 2023 15:22:08 +0100 Subject: [PATCH 047/116] abstract culture selection into its own component --- .../edit-language-workspace-view.element.ts | 84 +++++------------ .../src/backoffice/shared/components/index.ts | 1 + .../input-culture-select.element.ts | 90 +++++++++++++++++++ 3 files changed, 113 insertions(+), 62 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 1671dddcd7..7c5bd3e7f2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -5,9 +5,10 @@ import { repeat } from 'lit/directives/repeat.js'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { UmbLanguageWorkspaceContext } from '../../language-workspace.context'; -import { UmbCultureRepository } from '../../../../../cultures/repository/culture.repository'; import { UmbLitElement } from '@umbraco-cms/element'; -import { CultureModel, LanguageModel } from '@umbraco-cms/backend-api'; +import { LanguageModel } from '@umbraco-cms/backend-api'; +import { UmbChangeEvent } from 'src/core/events'; +import UmbInputCultureSelectElement from 'src/backoffice/shared/components/input-culture-select/input-culture-select.element'; @customElement('umb-edit-language-workspace-view') export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { @@ -51,17 +52,10 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { @state() private _languages: LanguageModel[] = []; - @state() - private _cultures: CultureModel[] = []; - - @state() - private _search = ''; - @state() private _startData: LanguageModel | null = null; #languageWorkspaceContext?: UmbLanguageWorkspaceContext; - #cultureRepository = new UmbCultureRepository(this); constructor() { super(); @@ -75,29 +69,13 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { }); } - protected async firstUpdated() { - const { data } = await this.#cultureRepository.requestCultures(); - if (data) { - this._cultures = data.items; - } - } - - #handleLanguageChange(event: Event) { - if (event instanceof UUIComboboxEvent) { - const target = event.composedPath()[0] as UUIComboboxElement; + #handleCultureChange(event: Event) { + if (event instanceof UmbChangeEvent) { + const target = event.target as UmbInputCultureSelectElement; const isoCode = target.value.toString(); + const cultureName = target.selectedCultureName; - if (isoCode) { - this.#languageWorkspaceContext?.setCulture(isoCode); - - // If the language name is not set, we set it to the name of the selected language. - if (!this.language?.name) { - const culture = this._cultures.find((culture) => culture.name === isoCode); - if (culture && culture.englishName) { - this.#languageWorkspaceContext?.setName(culture.englishName); - } - } - } else { + if (!isoCode) { // If the isoCode is empty, we reset the value to the original value. // Provides a way better UX //TODO: Maybe the combobox should implement something similar? @@ -107,15 +85,18 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { target.addEventListener('close', resetFunction, { once: true }); target.addEventListener('blur', resetFunction, { once: true }); + return; + } + + this.#languageWorkspaceContext?.setCulture(isoCode); + + // If the language name is not set, we set it to the name of the selected language. + if (!this.language?.name && cultureName) { + this.#languageWorkspaceContext?.setName(cultureName); } } } - #handleSearchChange(event: Event) { - const target = event.composedPath()[0] as UUIComboboxElement; - this._search = target.search; - } - #handleDefaultChange(event: UUIBooleanInputEvent) { if (event instanceof UUIBooleanInputEvent) { const target = event.composedPath()[0] as UUIToggleElement; @@ -137,12 +118,6 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { } } - get #filteredCultures(): Array { - return this._cultures.filter((culture) => { - return culture.englishName?.toLowerCase().includes(this._search.toLowerCase()); - }); - } - get #fallbackLanguages() { return this._languages.filter((language) => { return language.isoCode !== this.language?.isoCode; @@ -153,10 +128,6 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { return this.#fallbackLanguages.find((language) => language.isoCode === this.language?.fallbackIsoCode); } - get #fromAvailableCultures() { - return this._cultures.find((culture) => culture.name === this.language?.isoCode); - } - #renderCultureWarning() { if (!this._startData?.isoCode || this._startData?.isoCode === this.language?.isoCode) return nothing; @@ -181,29 +152,17 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement {
    - - - ${repeat( - this.#filteredCultures, - (language) => language.name, - (language) => - html` - ${language.englishName} - ` - )} - - + ${this.#renderCultureWarning()}
    +
    ${this.language.isoCode}
    +
    + diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts index 565fd6e060..6b46d30149 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts @@ -28,6 +28,7 @@ import '../entity-actions/entity-action-list.element'; import './input-media-picker/input-media-picker.element'; import './input-document-picker/input-document-picker.element'; +import './input-culture-select/input-culture-select.element'; import './empty-state/empty-state.element'; import './color-picker/color-picker.element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts new file mode 100644 index 0000000000..d6fbf4da05 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts @@ -0,0 +1,90 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, state } from 'lit/decorators.js'; +import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; +import { repeat } from 'lit/directives/repeat.js'; +import { UUIComboboxElement } from '@umbraco-ui/uui'; +import { UmbCultureRepository } from '../../../settings/cultures/repository/culture.repository'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { CultureModel } from '@umbraco-cms/backend-api'; +import { UmbChangeEvent } from 'src/core/events'; + +@customElement('umb-input-culture-select') +export class UmbInputCultureSelectElement extends FormControlMixin(UmbLitElement) { + static styles = [UUITextStyles, css``]; + + @state() + private _cultures: CultureModel[] = []; + + @state() + private _search = ''; + + public selectedCultureName?: string; + + #cultureRepository = new UmbCultureRepository(this); + + protected getFormElement() { + return undefined; + } + + protected async firstUpdated() { + const { data } = await this.#cultureRepository.requestCultures(); + if (data) { + this._cultures = data.items; + } + } + + #onSearchChange(event: Event) { + event.stopPropagation(); + const target = event.composedPath()[0] as UUIComboboxElement; + this._search = target.search; + } + + #onCultureChange(event: Event) { + event.stopPropagation(); + const target = event.composedPath()[0] as UUIComboboxElement; + this._value = target.value; + const culture = this._cultures.find((culture) => culture.name === this._value); + this.selectedCultureName = culture?.englishName; + this.dispatchEvent(new UmbChangeEvent()); + } + + get #filteredCultures(): Array { + return this._cultures.filter((culture) => { + return culture.englishName?.toLowerCase().includes(this._search.toLowerCase()); + }); + } + + get #fromAvailableCultures() { + return this._cultures.find((culture) => culture.name === this.value); + } + + render() { + return html` + + ${repeat( + this.#filteredCultures, + (culture) => culture.name, + (culture) => + html` + ${culture.englishName} + ` + )} + + `; + } +} + +export default UmbInputCultureSelectElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-culture-select': UmbInputCultureSelectElement; + } +} From 32db24f16a23113e561fc4b9b733b2c597e11471 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 15:24:00 +0100 Subject: [PATCH 048/116] add correct event types --- .../input-culture-select/input-culture-select.element.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts index d6fbf4da05..050a942a1c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts @@ -4,7 +4,7 @@ import { customElement, state } from 'lit/decorators.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { repeat } from 'lit/directives/repeat.js'; -import { UUIComboboxElement } from '@umbraco-ui/uui'; +import { UUIComboboxElement, UUIComboboxEvent } from '@umbraco-ui/uui'; import { UmbCultureRepository } from '../../../settings/cultures/repository/culture.repository'; import { UmbLitElement } from '@umbraco-cms/element'; import { CultureModel } from '@umbraco-cms/backend-api'; @@ -35,13 +35,13 @@ export class UmbInputCultureSelectElement extends FormControlMixin(UmbLitElement } } - #onSearchChange(event: Event) { + #onSearchChange(event: UUIComboboxEvent) { event.stopPropagation(); const target = event.composedPath()[0] as UUIComboboxElement; this._search = target.search; } - #onCultureChange(event: Event) { + #onCultureChange(event: UUIComboboxEvent) { event.stopPropagation(); const target = event.composedPath()[0] as UUIComboboxElement; this._value = target.value; From db0d3b2502a9261c62c4339b4b73f7db79573c24 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 15:34:51 +0100 Subject: [PATCH 049/116] don't show undefined in input --- .../languages/workspace/language/language-workspace.element.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts index 48f9bb6d9b..409d475777 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.element.ts @@ -2,6 +2,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; import { UmbWorkspaceEntityElement } from '../../../../shared/components/workspace/workspace-entity-element.interface'; import { UmbLanguageWorkspaceContext } from './language-workspace.context'; import { UmbLitElement } from '@umbraco-cms/element'; @@ -64,7 +65,7 @@ export class UmbLanguageWorkspaceElement extends UmbLitElement implements UmbWor - + `; From 4892c69be06871dd8ff1b1d26a129c9c8a5932f1 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 15:39:34 +0100 Subject: [PATCH 050/116] fix wrong repository alias --- .../settings/languages/workspace/language/manifests.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts index 157ef29fc5..922de44ddc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts @@ -1,5 +1,6 @@ import { UmbSaveWorkspaceAction } from '../../../../shared/workspace-actions/save.action'; import type { ManifestWorkspace, ManifestWorkspaceAction, ManifestWorkspaceView } from '@umbraco-cms/models'; +import { LANGUAGE_REPOSITORY_ALIAS } from '../../repository/manifests'; const workspace: ManifestWorkspace = { type: 'workspace', @@ -37,7 +38,7 @@ const workspaceActions: Array = [ look: 'primary', color: 'positive', label: 'Save', - repositoryAlias: 'Umb.Repository.Language', + repositoryAlias: LANGUAGE_REPOSITORY_ALIAS, api: UmbSaveWorkspaceAction, }, }, From 65f425fa45eca742dd140f6d3921bea91591db63 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 15:42:06 +0100 Subject: [PATCH 051/116] fix import order --- .../settings/languages/workspace/language/manifests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts index 922de44ddc..b2581f133d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/manifests.ts @@ -1,6 +1,6 @@ import { UmbSaveWorkspaceAction } from '../../../../shared/workspace-actions/save.action'; -import type { ManifestWorkspace, ManifestWorkspaceAction, ManifestWorkspaceView } from '@umbraco-cms/models'; import { LANGUAGE_REPOSITORY_ALIAS } from '../../repository/manifests'; +import type { ManifestWorkspace, ManifestWorkspaceAction, ManifestWorkspaceView } from '@umbraco-cms/models'; const workspace: ManifestWorkspace = { type: 'workspace', From fed576bcaf6d71ad7a4953203c55307549994b8d Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Thu, 16 Feb 2023 15:57:38 +0100 Subject: [PATCH 052/116] color picker --- .../input-color-picker.element.ts | 8 +-- ...property-editor-ui-color-picker.element.ts | 11 ++-- .../src/core/mocks/data/data-type.data.ts | 55 +++++++++++++++++-- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-color-picker/input-color-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-color-picker/input-color-picker.element.ts index 49c62beae4..4ffd7b1fc0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-color-picker/input-color-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-color-picker/input-color-picker.element.ts @@ -13,7 +13,7 @@ export class UmbInputColorPickerElement extends FormControlMixin(UmbLitElement) showLabels = false; @property() - colors?: string[]; + swatches?: any[]; constructor() { super(); @@ -36,10 +36,10 @@ export class UmbInputColorPickerElement extends FormControlMixin(UmbLitElement) } private _renderColors() { - return html`${this.colors?.map((color) => { + return html`${this.swatches?.map((swatch) => { return html``; })}`; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts index 453b358ee0..802e71ec50 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/color-picker/property-editor-ui-color-picker.element.ts @@ -19,15 +19,16 @@ export class UmbPropertyEditorUIColorPickerElement extends UmbLitElement { private _includeLabels = false; @state() - private _colorSwatches: string[] = []; + private _swatches: any[] = []; @property({ type: Array, attribute: false }) public set config(config: Array) { - const includeLabels = config.find((x) => x.alias === 'includeLabels'); + const includeLabels = config.find((x) => x.alias === 'useLabels'); if (includeLabels) this._includeLabels = includeLabels.value; - const colorSwatches = config.find((x) => x.alias === 'colors'); - if (colorSwatches) this._colorSwatches = colorSwatches.value; + const colorSwatches = config.find((x) => x.alias === 'items'); + if (colorSwatches) + this._swatches = (colorSwatches.value as any[]).slice().sort((a, b) => a.sortOrder - b.sortOrder); } private _onChange(event: UUIColorSwatchesEvent) { @@ -38,7 +39,7 @@ export class UmbPropertyEditorUIColorPickerElement extends UmbLitElement { render() { return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts index 2e60cf6417..c851beb0e4 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/data-type.data.ts @@ -54,12 +54,58 @@ export const data: Array = [ propertyEditorUiAlias: 'Umb.PropertyEditorUI.ColorPicker', data: [ { - alias: 'includeLabels', - value: false, + alias: 'useLabels', + value: true, }, { - alias: 'colors', - value: ['#000000', '#373737', '#9e9e9e', '#607d8b', '#2196f3', '#03a9f4', '#3f51b5', '#9c27b0', '#673ab7'], + alias: 'items', + value: [ + { + value: '#000000', + label: 'Black', + sortOrder: 0, + }, + { + value: '#373737', + label: 'Dark', + sortOrder: 1, + }, + { + value: '#9e9e9e', + label: 'Light', + sortOrder: 2, + }, + { + value: '#607d8b', + label: 'Sage', + sortOrder: 3, + }, + { + value: '#2196f3', + label: 'Sapphire', + sortOrder: 4, + }, + { + value: '#03a9f4', + label: 'Sky', + sortOrder: 5, + }, + { + value: '#3f51b5', + label: 'Blue', + sortOrder: 6, + }, + { + value: '#9c27b0', + label: 'Magenta', + sortOrder: 8, + }, + { + value: '#673ab7', + label: 'Purps', + sortOrder: 7, + }, + ], }, ], }, @@ -86,6 +132,7 @@ export const data: Array = [ propertyEditorUiAlias: 'Umb.PropertyEditorUI.EyeDropper', data: [ { + //showPalette alias: 'palette', value: [ '#d0021b', From 386741e23e4f6a7969b82803f6925cf18cf36b3d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Feb 2023 17:26:00 +0100 Subject: [PATCH 053/116] wip language picker --- .../edit-language-workspace-view.element.ts | 18 +- .../src/backoffice/shared/components/index.ts | 1 + .../input-language-picker.element.ts | 179 ++++++++++++++++++ 3 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 7c5bd3e7f2..cb1427dbec 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -188,23 +188,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { - - - ${repeat( - this.#fallbackLanguages, - (language) => language.isoCode, - (language) => - html` - ${language.name} - ` - )} - - +
    `; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts index 6b46d30149..42eb476ac9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts @@ -28,6 +28,7 @@ import '../entity-actions/entity-action-list.element'; import './input-media-picker/input-media-picker.element'; import './input-document-picker/input-document-picker.element'; +import './input-language-picker/input-language-picker.element'; import './input-culture-select/input-culture-select.element'; import './empty-state/empty-state.element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts new file mode 100644 index 0000000000..990d1e662d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts @@ -0,0 +1,179 @@ +import { css, html, nothing } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, property, state } from 'lit/decorators.js'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; +import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; +import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../core/modal'; +import { UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN } from '../../../documents/documents/repository/document.tree.store'; +import { UmbLitElement } from '@umbraco-cms/element'; +import type { FolderTreeItemModel, LanguageModel } from '@umbraco-cms/backend-api'; +import type { UmbObserverController } from '@umbraco-cms/observable-api'; +import { UmbLanguageRepository } from 'src/backoffice/settings/languages/repository/language.repository'; + +@customElement('umb-input-language-picker') +export class UmbInputLanguagePickerElement extends FormControlMixin(UmbLitElement) { + static styles = [ + UUITextStyles, + css` + #add-button { + width: 100%; + } + `, + ]; + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default undefined + */ + @property({ type: Number }) + min?: number; + + /** + * Min validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + minMessage = 'This field need more items'; + + /** + * This is a maximum amount of selected items in this input. + * @type {number} + * @attr + * @default undefined + */ + @property({ type: Number }) + max?: number; + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + private _selectedIsoCodes: Array = []; + public get selectedIsoCodes(): Array { + return this._selectedIsoCodes; + } + public set selectedIsoCodes(keys: Array) { + this._selectedIsoCodes = keys; + super.value = keys.join(','); + this._observePickedItems(); + } + + @property() + public set value(keysString: string) { + if (keysString !== this._value) { + this.selectedIsoCodes = keysString.split(/[ ,]+/); + } + } + + @state() + private _items?: Array; + + private _modalService?: UmbModalService; + private _repository?: UmbLanguageRepository; + private _pickedItemsObserver?: UmbObserverController; + + constructor() { + super(); + + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this._selectedIsoCodes.length < this.min + ); + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this._selectedIsoCodes.length > this.max + ); + + this.consumeContext('UmbLanguageRepository', (instance) => { + debugger; + this._repository = instance; + this._observePickedItems(); + }); + + this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { + this._modalService = instance; + }); + } + + protected getFormElement() { + return undefined; + } + + private async _observePickedItems() { + this._pickedItemsObserver?.destroy(); + if (!this._repository) return; + + const { asObservable } = await this._repository.requestItems(this._selectedIsoCodes); + + this._pickedItemsObserver = this.observe(asObservable(), (items) => { + this._items = items; + }); + } + + private _openPicker() { + const modalHandler = this._modalService?.languagePicker({ + multiple: this.max === 1 ? false : true, + selection: [...this._selectedIsoCodes], + }); + modalHandler?.onClose().then(({ selection }: any) => { + this._setSelection(selection); + }); + } + + private _removeItem(item: FolderTreeItemModel) { + const modalHandler = this._modalService?.confirm({ + color: 'danger', + headline: `Remove ${item.name}?`, + content: 'Are you sure you want to remove this item', + confirmLabel: 'Remove', + }); + + modalHandler?.onClose().then(({ confirmed }) => { + if (confirmed) { + const newSelection = this._selectedIsoCodes.filter((value) => value !== item.key); + this._setSelection(newSelection); + } + }); + } + + private _setSelection(newSelection: Array) { + this.selectedIsoCodes = newSelection; + this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); + } + + render() { + return html` + ${this._items?.map((item) => this._renderItem(item))} + Add + `; + } + + private _renderItem(item: FolderTreeItemModel) { + return html` + + + + this._removeItem(item)} label="Remove ${item.name}">Remove + + + `; + } +} + +export default UmbInputLanguagePickerElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-language-picker': UmbInputLanguagePickerElement; + } +} From 16cde515c2698df89b7a3bcd1cfdf59f3b645ed9 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 16 Feb 2023 20:12:39 +0000 Subject: [PATCH 054/116] Update src/backoffice/shared/components/debug/debug.modal.element.ts Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> --- .../backoffice/shared/components/debug/debug.modal.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts index 56f898c6d5..d8e85fa64f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts @@ -4,7 +4,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { UmbModalHandler, UmbModalLayoutElement } from '@umbraco-cms/modal'; @customElement('umb-debug-modal-layout') -export class UmbDebugModalLayout extends UmbModalLayoutElement { +export class UmbDebugModalLayout extends UmbModalLayoutElement { static styles = [ UUITextStyles, css` From 2e3aafbc01692215fe70bf353e4f19f2e776db88 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 16 Feb 2023 20:12:54 +0000 Subject: [PATCH 055/116] Update src/backoffice/shared/components/debug/debug.element.ts Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> --- .../src/backoffice/shared/components/debug/debug.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index 726fa1cb6c..3a9e3d9272 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -56,7 +56,7 @@ export class UmbDebug extends UmbLitElement { enabled = false; @property({ reflect: true, type: Boolean }) - useDialog = false; + dialog = false; @property() contexts = new Map(); From 64ecdc69aace616e8b532a32b5e69b88457d7bb3 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 16 Feb 2023 20:13:42 +0000 Subject: [PATCH 056/116] Update src/backoffice/shared/components/debug/debug.element.ts Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> --- .../src/backoffice/shared/components/debug/debug.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index 3a9e3d9272..76c4c34c89 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -92,7 +92,7 @@ export class UmbDebug extends UmbLitElement { render() { if (this.enabled) { - return this.useDialog ? this._renderDialog() : this._renderPanel(); + return this.dialog ? this._renderDialog() : this._renderPanel(); } else { return nothing; } From a42cc1bdb256ea579781ea60a2dde511490bd696 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 16 Feb 2023 20:13:52 +0000 Subject: [PATCH 057/116] Update src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> --- .../published-status/dashboard-published-status.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts index c9aee3291c..e7693ba27c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts @@ -133,7 +133,7 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement { render() { return html` - +

    ${this._publishedStatusText}

    Date: Thu, 16 Feb 2023 20:26:36 +0000 Subject: [PATCH 058/116] Remove modalHandler as its in the base class --- .../backoffice/shared/components/debug/debug.modal.element.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts index d8e85fa64f..8b93a125d9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts @@ -55,10 +55,6 @@ export class UmbDebugModalLayout extends UmbModalLayoutElement { ]; - // the modal handler will be injected into the element when the modal is opened. - @property({ attribute: false }) - modalHandler?: UmbModalHandler; - private _handleClose() { /* Optional data of any type can be applied to the close method to pass it to the modal parent through the onClose promise. */ From 4e41078d46c713ac45c088bb248eafd68af36722 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 16 Feb 2023 20:26:58 +0000 Subject: [PATCH 059/116] Remove comment copied from example code in Storybook docs --- .../backoffice/shared/components/debug/debug.modal.element.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts index 8b93a125d9..b47b026e6c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts @@ -54,11 +54,7 @@ export class UmbDebugModalLayout extends UmbModalLayoutElement { `, ]; - private _handleClose() { - /* Optional data of any type can be applied to the close method to pass it - to the modal parent through the onClose promise. */ - //this.modalHandler?.close('MY DATA'); this.modalHandler?.close(); } From 5374215856eb3cdda15dd6aa0092aa927cc9b9a2 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 16 Feb 2023 20:27:35 +0000 Subject: [PATCH 060/116] Move import for dialog into the main debug component TODO: Ask Jacob on how to lazily load it --- .../src/backoffice/shared/components/debug/debug.element.ts | 3 +++ .../src/backoffice/shared/components/index.ts | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index 76c4c34c89..f05c0bf8f1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -5,6 +5,9 @@ import { UmbContextDebugRequest } from '@umbraco-cms/context-api'; import { UmbLitElement } from '@umbraco-cms/element'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; +// TODO: Ask Jacob how to lazily load this depending on a property +import './debug.modal.element'; + @customElement('umb-debug') export class UmbDebug extends UmbLitElement { static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts index b09a13320e..c9602d9759 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts @@ -31,5 +31,4 @@ import './input-document-picker/input-document-picker.element'; import './empty-state/empty-state.element'; import './color-picker/color-picker.element'; -import './debug/debug.element'; -import './debug/debug.modal.element'; \ No newline at end of file +import './debug/debug.element'; \ No newline at end of file From afd5b6ad40ca0d603f5f63d2208b75389984830a Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 16 Feb 2023 21:11:50 +0000 Subject: [PATCH 061/116] Make modal small - looks weird with too much whitespace Remove assigning to const as we don't need the onClose callback - nothing happens in the open dialog to be notified about --- .../shared/components/debug/debug.element.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index f05c0bf8f1..6f845983d5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -107,11 +107,13 @@ export class UmbDebug extends UmbLitElement { } private _openDialog() { - const modalHandler = this._modalService?.open('umb-debug-modal-layout', { size: 'medium', type: 'sidebar', data:{ contexts: this.contexts }}); - - modalHandler?.onClose().then((data) => { - // if any data is supplied on close, it will be available here. - console.log('modal closed data', data); + // Open a modal that uses the HTML component called 'umb-debug-modal-layout' + this._modalService?.open('umb-debug-modal-layout', { + size: 'small', + type: 'sidebar', + data:{ + content: this._renderContextAliases() + } }); } From a058ce01b801ff25e024611efd144509c999b66e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 16 Feb 2023 21:19:58 +0000 Subject: [PATCH 062/116] Rename a few variables and try to improve the markup for use in dialog & non dialog mode as its now shared --- .../shared/components/debug/debug.element.ts | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index 6f845983d5..324d7b84d9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -138,7 +138,6 @@ export class UmbDebug extends UmbLitElement {
    -

    Context Aliases to consume

      ${this._renderContextAliases()}
    @@ -148,13 +147,14 @@ export class UmbDebug extends UmbLitElement { } private _renderContextAliases() { - const aliases = []; + const contextsTemplates: TemplateResult[] = []; for (const [alias, instance] of this.contexts) { - aliases.push( - html`
  • + contextsTemplates.push( + html` +
  • Context: ${alias} - (${typeof instance}) + (${typeof instance})
      ${this._renderInstance(instance)}
    @@ -162,20 +162,31 @@ export class UmbDebug extends UmbLitElement { ); } - return aliases; + return contextsTemplates; } private _renderInstance(instance: any) { - const instanceKeys: TemplateResult[] = []; + const instanceTemplates: TemplateResult[] = []; + // TODO: WB - Maybe make this a switch statement? if (typeof instance === 'function') { - return instanceKeys.push(html`
  • Callable Function
  • `); + return instanceTemplates.push(html`
  • Callable Function
  • `); } else if (typeof instance === 'object') { const methodNames = this.getClassMethodNames(instance); if (methodNames.length) { - instanceKeys.push(html`
  • Methods - ${methodNames.join(', ')}
  • `); + instanceTemplates.push( + html` +
  • + Methods +
      + ${methodNames.map((methodName) => html`
    • ${methodName}
    • `)} +
    +
  • + `); } + instanceTemplates.push(html`
  • Properties
  • `); + for (const key in instance) { if (key.startsWith('_')) { continue; @@ -183,16 +194,16 @@ export class UmbDebug extends UmbLitElement { const value = instance[key]; if (typeof value === 'string') { - instanceKeys.push(html`
  • ${key} = ${value}
  • `); + instanceTemplates.push(html`
  • ${key} = ${value}
  • `); } else { - instanceKeys.push(html`
  • ${key} (${typeof value})
  • `); + instanceTemplates.push(html`
  • ${key} (${typeof value})
  • `); } } } else { - instanceKeys.push(html`
  • Context is a primitive with value: ${instance}
  • `); + instanceTemplates.push(html`
  • Context is a primitive with value: ${instance}
  • `); } - return instanceKeys; + return instanceTemplates; } private getClassMethodNames(klass: any) { From fb54bd7ddd4bafa4d7f2e1373a2b227ba8e31aec Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 16 Feb 2023 21:20:16 +0000 Subject: [PATCH 063/116] Remove uneeded code now --- .../components/debug/debug.modal.element.ts | 83 +------------------ 1 file changed, 4 insertions(+), 79 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts index b47b026e6c..eed378d006 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts @@ -1,7 +1,7 @@ -import { css, html, nothing, TemplateResult } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; +import { css, html } from 'lit'; +import { customElement } from 'lit/decorators.js'; import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { UmbModalHandler, UmbModalLayoutElement } from '@umbraco-cms/modal'; +import { UmbModalLayoutElement } from '@umbraco-cms/modal'; @customElement('umb-debug-modal-layout') export class UmbDebugModalLayout extends UmbModalLayoutElement { @@ -65,87 +65,12 @@ export class UmbDebugModalLayout extends UmbModalLayoutElement { Debug: Contexts - ${this._renderContextAliases()} + ${this.data.content} Close `; } - - private _renderContextAliases() { - if(!this.data) { - return nothing; - } - - const aliases = []; - for (const [alias, instance] of this.data.contexts) { - aliases.push( - html` -
    -

    ${alias} ${typeof instance}

    - ${this._renderInstance(instance)} -
    ` - ); - } - - return aliases; - } - - private _renderInstance(instance: any) { - const instanceKeys: TemplateResult[] = []; - - if (typeof instance === 'function') { - return instanceKeys.push(html`
  • Callable Function
  • `); - } else if (typeof instance === 'object') { - const methodNames = this.getClassMethodNames(instance); - if (methodNames.length) { - instanceKeys.push( - html` -

    Methods

    -
      - ${methodNames.map((methodName) => html`
    • ${methodName}
    • `)} -
    - `); - } - - instanceKeys.push(html`

    Properties

    `); - - for (const key in instance) { - if (key.startsWith('_')) { - continue; - } - - const value = instance[key]; - if (typeof value === 'string') { - instanceKeys.push(html`
  • ${key} = ${value}
  • `); - } else { - instanceKeys.push(html`
  • ${key} Type (${typeof value})
  • `); - } - } - } else { - instanceKeys.push(html`
  • Context is a primitive with value: ${instance}
  • `); - } - - return instanceKeys; - } - - - private getClassMethodNames(klass: any) { - const isGetter = (x: any, name: string): boolean => !!(Object.getOwnPropertyDescriptor(x, name) || {}).get; - const isFunction = (x: any, name: string): boolean => typeof x[name] === 'function'; - const deepFunctions = (x: any): any => - x !== Object.prototype && - Object.getOwnPropertyNames(x) - .filter((name) => isGetter(x, name) || isFunction(x, name)) - .concat(deepFunctions(Object.getPrototypeOf(x)) || []); - const distinctDeepFunctions = (klass: any) => Array.from(new Set(deepFunctions(klass))); - - const allMethods = - typeof klass.prototype === 'undefined' - ? distinctDeepFunctions(klass) - : Object.getOwnPropertyNames(klass.prototype); - return allMethods.filter((name: any) => name !== 'constructor' && !name.startsWith('_')); - } } declare global { From 6bf96716ea2184a1bcb2e342a55c9ce5de9fccac Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 16 Feb 2023 21:24:48 +0000 Subject: [PATCH 064/116] Specify an interface/type for the modal like others in codebase --- .../shared/components/debug/debug.modal.element.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts index eed378d006..d86045c85e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts @@ -1,10 +1,14 @@ -import { css, html } from 'lit'; +import { css, html, TemplateResult } from 'lit'; import { customElement } from 'lit/decorators.js'; import { UUITextStyles } from '@umbraco-ui/uui-css'; import { UmbModalLayoutElement } from '@umbraco-cms/modal'; +export interface UmbDebugModalData { + content: TemplateResult | string; +} + @customElement('umb-debug-modal-layout') -export class UmbDebugModalLayout extends UmbModalLayoutElement { +export class UmbDebugModalLayout extends UmbModalLayoutElement { static styles = [ UUITextStyles, css` @@ -65,7 +69,7 @@ export class UmbDebugModalLayout extends UmbModalLayoutElement { Debug: Contexts - ${this.data.content} + ${this.data?.content} Close From 430ccc7ea405eee444ebb20d944d51e9e25f7bff Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:16:44 +0100 Subject: [PATCH 065/116] set class as default export --- .../components/debug/debug.modal.element.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts index d86045c85e..d9d370aaf0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.modal.element.ts @@ -8,7 +8,7 @@ export interface UmbDebugModalData { } @customElement('umb-debug-modal-layout') -export class UmbDebugModalLayout extends UmbModalLayoutElement { +export default class UmbDebugModalLayout extends UmbModalLayoutElement { static styles = [ UUITextStyles, css` @@ -34,8 +34,8 @@ export class UmbDebugModalLayout extends UmbModalLayoutElement - - Debug: Contexts - - - ${this.data?.content} - + Debug: Contexts + ${this.data?.content} Close `; From 6ab0bd77d11f2dd1d1f5c2560ff1fbb178d7ffa4 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:17:02 +0100 Subject: [PATCH 066/116] add properties to a sub-ul list --- .../shared/components/debug/debug.element.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index 324d7b84d9..d4f23a9f0f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -182,10 +182,11 @@ export class UmbDebug extends UmbLitElement { ${methodNames.map((methodName) => html`
  • ${methodName}
  • `)} - `); + ` + ); } - instanceTemplates.push(html`
  • Properties
  • `); + const props: TemplateResult[] = []; for (const key in instance) { if (key.startsWith('_')) { @@ -194,11 +195,20 @@ export class UmbDebug extends UmbLitElement { const value = instance[key]; if (typeof value === 'string') { - instanceTemplates.push(html`
  • ${key} = ${value}
  • `); + props.push(html`
  • ${key} = ${value}
  • `); } else { - instanceTemplates.push(html`
  • ${key} (${typeof value})
  • `); + props.push(html`
  • ${key} (${typeof value})
  • `); } } + + instanceTemplates.push(html` +
  • + Properties +
      + ${props} +
    +
  • + `); } else { instanceTemplates.push(html`
  • Context is a primitive with value: ${instance}
  • `); } From 29667b8bb70c23cfc6595a03b9f75954c935331d Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:17:30 +0100 Subject: [PATCH 067/116] lazily load the modal layout --- .../shared/components/debug/debug.element.ts | 73 ++++++++----------- 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts index d4f23a9f0f..84aff299aa 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/debug/debug.element.ts @@ -5,21 +5,18 @@ import { UmbContextDebugRequest } from '@umbraco-cms/context-api'; import { UmbLitElement } from '@umbraco-cms/element'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; -// TODO: Ask Jacob how to lazily load this depending on a property -import './debug.modal.element'; - @customElement('umb-debug') export class UmbDebug extends UmbLitElement { static styles = [ UUITextStyles, - css` + css` #container { display: block; font-family: monospace; z-index: 10000; - position:relative; + position: relative; width: 100%; padding: 10px 0; } @@ -75,14 +72,13 @@ export class UmbDebug extends UmbLitElement { this._modalService = modalService; }); } - + connectedCallback(): void { super.connectedCallback(); // Dispatch it this.dispatchEvent( new UmbContextDebugRequest((contexts: Map) => { - // The Contexts are collected // When travelling up through the DOM from this element // to the root of which then uses the callback prop @@ -94,56 +90,52 @@ export class UmbDebug extends UmbLitElement { } render() { - if (this.enabled) { - return this.dialog ? this._renderDialog() : this._renderPanel(); + if (this.enabled) { + return this.dialog ? this._renderDialog() : this._renderPanel(); } else { return nothing; } - } private _toggleDebugPane() { this._debugPaneOpen = !this._debugPaneOpen; } - private _openDialog() { + private async _openDialog() { // Open a modal that uses the HTML component called 'umb-debug-modal-layout' - this._modalService?.open('umb-debug-modal-layout', { - size: 'small', - type: 'sidebar', - data:{ - content: this._renderContextAliases() - } + await import('./debug.modal.element.js'); + this._modalService?.open('umb-debug-modal-layout', { + size: 'medium', + type: 'sidebar', + data: { + content: this._renderContextAliases(), + }, }); } - - private _renderDialog() { - return html` -
    - - Debug - -
    `; + return html`
    + + Debug + +
    `; } - private _renderPanel(){ - return html` -
    - - - Debug - + private _renderPanel() { + return html`
    + + + Debug + -
    -
    -
      - ${this._renderContextAliases()} -
    -
    +
    +
    +
      + ${this._renderContextAliases()} +
    -
    `; +
    +
    `; } private _renderContextAliases() { @@ -151,8 +143,7 @@ export class UmbDebug extends UmbLitElement { for (const [alias, instance] of this.contexts) { contextsTemplates.push( - html` -
  • + html`
  • Context: ${alias} (${typeof instance})
  • - - + +
    `; diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts index d05474cf87..4e32140f45 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts @@ -82,9 +82,9 @@ export class UmbPickerLayoutUserGroupElement extends UmbModalLayoutPickerBase { ${this._userGroups.map( (item) => html`
    this._handleItemClick(item.key)} + @click=${() => this.handleSelection(item.key)} @keydown=${(e: KeyboardEvent) => this._handleKeydown(e, item.key)} - class=${this._isSelected(item.key) ? 'item selected' : 'item'}> + class=${this.isSelected(item.key) ? 'item selected' : 'item'}> ${item.name}
    @@ -93,8 +93,8 @@ export class UmbPickerLayoutUserGroupElement extends UmbModalLayoutPickerBase {
    - - + +
    `; diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts index 452d26ca4f..ca74c4aefb 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts @@ -86,9 +86,9 @@ export class UmbPickerLayoutUserElement extends UmbModalLayoutPickerBase { ${this._users.map( (item) => html`
    this._handleItemClick(item.key)} + @click=${() => this.handleSelection(item.key)} @keydown=${(e: KeyboardEvent) => this._handleKeydown(e, item.key)} - class=${this._isSelected(item.key) ? 'item selected' : 'item'}> + class=${this.isSelected(item.key) ? 'item selected' : 'item'}> ${item.name}
    @@ -97,8 +97,8 @@ export class UmbPickerLayoutUserElement extends UmbModalLayoutPickerBase {
    - - + +
    `; diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts b/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts index 0ed747171e..85ec632abd 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/modal.service.ts @@ -5,6 +5,7 @@ import './layouts/media-picker/modal-layout-media-picker.element'; import './layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element'; import './layouts/modal-layout-current-user.element'; import './layouts/icon-picker/modal-layout-icon-picker.element'; +import '../../backoffice/settings/languages/language-picker/language-picker-modal-layout.element'; import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar'; import { BehaviorSubject } from 'rxjs'; @@ -16,6 +17,7 @@ import type { UmbModalPropertyEditorUIPickerData } from './layouts/property-edit import type { UmbModalMediaPickerData } from './layouts/media-picker/modal-layout-media-picker.element'; import { UmbModalHandler } from './modal-handler'; import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UmbLanguagePickerModalData } from '../../backoffice/settings/languages/language-picker/language-picker-modal-layout.element'; export type UmbModalType = 'dialog' | 'sidebar'; @@ -25,7 +27,9 @@ export interface UmbModalOptions { data?: UmbModalData; } -// TODO: Should this be called UmbModalContext ? as we don't have 'services' as a term. +// TODO: rename to UmbModalContext +// TODO: we should find a way to easily open a modal without adding custom methods to this context. It would result in a better separation of concerns. +// TODO: move all layouts into their correct "silo" folders. User picker should live with users etc. export class UmbModalService { // TODO: Investigate if we can get rid of HTML elements in our store, so we can use one of our states. #modals = new BehaviorSubject(>[]); @@ -106,6 +110,16 @@ export class UmbModalService { return this.open('umb-modal-layout-change-password', { data, type: 'dialog' }); } + /** + * Opens a language picker sidebar modal + * @public + * @return {*} {UmbModalHandler} + * @memberof UmbModalService + */ + public languagePicker(data: UmbLanguagePickerModalData): UmbModalHandler { + return this.open('umb-language-picker-modal-layout', { data, type: 'sidebar' }); + } + /** * Opens a modal or sidebar modal * @public From 8b33b62f8ce12437bc90f41a2fbfea24c833e1a5 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:11:10 +0100 Subject: [PATCH 084/116] add setter for multi url to handle form value, and added change event after updates --- .../input-multi-url-picker.element.ts | 53 +++++++++++++------ ...erty-editor-ui-multi-url-picker.element.ts | 7 ++- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts index 439804e744..6651373138 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-multi-url-picker/input-multi-url-picker.element.ts @@ -17,6 +17,12 @@ export interface MultiUrlData { url?: string; } +/** + * @element umb-input-multi-url-picker + * @fires change - when the value of the input changes + * @fires blur - when the input loses focus + * @fires focus - when the input gains focus + */ @customElement('umb-input-multi-url-picker') export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElement) { static styles = [ @@ -77,16 +83,28 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen ignoreUserStartNodes?: boolean; /** - * @type {"small" | "medium" | "large"} + * @type {UUIModalSidebarSize} * @attr * @default "small" */ @property() overlaySize?: UUIModalSidebarSize; - @property({ attribute: 'urls' }) - multiUrls: Array = []; + /** + * @type {Array} + * @default [] + */ + @property({ attribute: false }) + set urls(data: Array) { + this._urls = data; + super.value = this._urls.map((x) => x.url).join(','); + } + get urls() { + return this._urls; + } + + private _urls: Array = []; private _modalService?: UmbModalService; constructor() { @@ -94,12 +112,12 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen this.addValidator( 'rangeUnderflow', () => this.minMessage, - () => !!this.min && this.multiUrls.length < this.min + () => !!this.min && this.urls.length < this.min ); this.addValidator( 'rangeOverflow', () => this.maxMessage, - () => !!this.max && this.multiUrls.length > this.max + () => !!this.max && this.urls.length > this.max ); this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { @@ -108,8 +126,20 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen } private _removeItem(index: number) { - this.multiUrls.splice(index, 1); + this.urls.splice(index, 1); + this._dispatchChangeEvent(); + } + + private _setSelection(selection: MultiUrlData, index?: number) { + if (index !== undefined && index >= 0) this.urls[index] = selection; + else this.urls.push(selection); + + this._dispatchChangeEvent(); + } + + private _dispatchChangeEvent() { this.requestUpdate(); + this.dispatchEvent(new CustomEvent('change', { composed: true, bubbles: true })); } private _openPicker(data?: MultiUrlData, index?: number) { @@ -131,17 +161,12 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen }); modalHandler?.onClose().then((newUrl: MultiUrlData) => { if (!newUrl) return; - - if (index !== undefined && index >= 0) this.multiUrls[index] = newUrl; - else this.multiUrls.push(newUrl); - - this.requestUpdate(); - //TODO: onChange event? + this._setSelection(newUrl, index); }); } render() { - return html`${this.multiUrls?.map((link, index) => this._renderItem(link, index))} + return html`${this.urls?.map((link, index) => this._renderItem(link, index))} Add`; } @@ -159,8 +184,6 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen } } -export default UmbInputMultiUrlPickerElement; - declare global { interface HTMLElementTagNameMap { 'umb-input-multi-url-picker': UmbInputMultiUrlPickerElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts index 281ed904b1..59d0abd9e4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts @@ -2,7 +2,10 @@ import { html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar'; -import { MultiUrlData } from '../../../../shared/components/input-multi-url-picker/input-multi-url-picker.element'; +import { + UmbInputMultiUrlPickerElement, + MultiUrlData, +} from '../../../../shared/components/input-multi-url-picker/input-multi-url-picker.element'; import { UmbLitElement } from '@umbraco-cms/element'; import { DataTypePropertyModel } from '@umbraco-cms/backend-api'; @@ -50,7 +53,7 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement { private _minNumber?: number; private _onChange(event: CustomEvent) { - //TODO: Do something about the values + this.value = (event.target as UmbInputMultiUrlPickerElement).urls; this.dispatchEvent(new CustomEvent('property-value-change')); } From 1af9f851a7f2ab4a471a6503d06d44e2f6577fa1 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 20 Feb 2023 13:59:54 +0000 Subject: [PATCH 085/116] Remove intro mdx as nothign useful in it --- .../src/stories/intro.stories.mdx | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/stories/intro.stories.mdx diff --git a/src/Umbraco.Web.UI.Client/src/stories/intro.stories.mdx b/src/Umbraco.Web.UI.Client/src/stories/intro.stories.mdx deleted file mode 100644 index 03f683de8b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/stories/intro.stories.mdx +++ /dev/null @@ -1,25 +0,0 @@ -import { Meta } from '@storybook/addon-docs'; - - - -# Introduction - -Welcome to the Storybook for the backoffice of Umbraco CMS. - -This is a living styleguide that documents the design system of Umbraco CMS. - -## Getting started - -To get started, you can either use the sidebar to navigate to a component, or you can use the search field to find a component. - -## Contributing - -If you want to contribute to the backoffice, you can do so by following the instructions in the [README](https://github.com/umbraco/Umbraco.CMS.Backoffice#readme). - -## License - -Umbraco CMS is licensed under the [MIT license](https://github.com/umbraco/Umbraco.CMS.Backoffice/blob/main/LICENSE). - -## Credits - -Umbraco CMS is created and maintained by [Umbraco HQ](https://umbraco.com). From 2968d825c95661f67fbf031e8159dfdbad9ddb12 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 20 Feb 2023 14:00:22 +0000 Subject: [PATCH 086/116] Rename the file to match the story name/title --- .../stories/{guides.stories.mdx => gettingstarted.stories.mdx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Umbraco.Web.UI.Client/src/stories/{guides.stories.mdx => gettingstarted.stories.mdx} (100%) diff --git a/src/Umbraco.Web.UI.Client/src/stories/guides.stories.mdx b/src/Umbraco.Web.UI.Client/src/stories/gettingstarted.stories.mdx similarity index 100% rename from src/Umbraco.Web.UI.Client/src/stories/guides.stories.mdx rename to src/Umbraco.Web.UI.Client/src/stories/gettingstarted.stories.mdx From e993436800e64657ea40101e68d9007feea38236 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 20 Feb 2023 14:01:37 +0000 Subject: [PATCH 087/116] Sort Guides collection first and then the Getting started guide inside it, after that all alphabetical https://storybook.js.org/docs/react/writing-stories/naming-components-and-hierarchy#sorting-stories --- src/Umbraco.Web.UI.Client/.storybook/preview.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/.storybook/preview.js b/src/Umbraco.Web.UI.Client/.storybook/preview.js index 73d791ca82..dbc166a597 100644 --- a/src/Umbraco.Web.UI.Client/.storybook/preview.js +++ b/src/Umbraco.Web.UI.Client/.storybook/preview.js @@ -94,6 +94,7 @@ export const parameters = { storySort: { method: 'alphabetical', includeNames: true, + order: ['Guides', ['Getting started'], '*'] }, }, actions: { argTypesRegex: '^on.*' }, From f25b57f9cf0372289a4df92ad5b91759f8b2a72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 20 Feb 2023 15:20:01 +0100 Subject: [PATCH 088/116] Implement Document Model and Document Type Model for Document Workspace (#531) * rename view * router for edit-tabs * composition document types * merge group structure * merge tabs * render group properties * styling * render properties from tab * render no tab tab * notes for later work * determine wether tab has properties * remove console.log * only show tabs navigation when more than one tab * variants observable * urls observable * templateKey observable * document observable * remove comments * notes for how to deal with variants. * notes for splitview and variants. * clean up * fix Not all code paths return a value * remove import * Add TODO --------- Co-authored-by: Mads Rasmussen --- .../workspace/document-workspace.context.ts | 189 +++++++++++------- .../documents/workspace/manifests.ts | 2 +- ...-workspace-view-edit-properties.element.ts | 149 ++++++++++++++ ...ocument-workspace-view-edit-tab.element.ts | 175 ++++++++++++++++ .../document-workspace-view-edit.element.ts | 178 +++++++++++++++++ .../workspace-view-document-edit.element.ts | 93 --------- .../workspace-content.element.ts | 2 + .../src/core/mocks/data/document-type.data.ts | 162 ++++++++++++--- .../src/core/mocks/data/document.data.ts | 188 ++++++++++++++++- 9 files changed, 941 insertions(+), 197 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-properties.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-tab.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/workspace-view-document-edit.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts index 9832192b15..8c9006ae8d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts @@ -2,7 +2,12 @@ import { UmbWorkspaceContext } from '../../../shared/components/workspace/worksp import { UmbDocumentRepository } from '../repository/document.repository'; import type { UmbWorkspaceEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-entity-context.interface'; import { UmbDocumentTypeRepository } from '../../document-types/repository/document-type.repository'; -import type { DocumentModel, DocumentTypeModel } from '@umbraco-cms/backend-api'; +import type { + DocumentModel, + DocumentTypeModel, + DocumentTypePropertyTypeContainerModel, + DocumentTypePropertyTypeModel, +} from '@umbraco-cms/backend-api'; import { partialUpdateFrozenArray, ObjectState, @@ -14,6 +19,8 @@ import { UmbControllerHostInterface } from '@umbraco-cms/controller'; // TODO: should this context be called DocumentDraft instead of workspace? or should the draft be part of this? +// TODO: Should we have a DocumentStructureContext and maybe even a DocumentDraftContext? + type EntityType = DocumentModel; export class UmbDocumentWorkspaceContext extends UmbWorkspaceContext @@ -23,29 +30,47 @@ export class UmbDocumentWorkspaceContext #host: UmbControllerHostInterface; #documentRepository: UmbDocumentRepository; #documentTypeRepository: UmbDocumentTypeRepository; - //#dataTypeRepository: UmbDataTypeRepository; - #data = new ObjectState(undefined); - documentTypeKey = this.#data.getObservablePart((data) => data?.contentTypeKey); + /** + * The document is the current stored version of the document. + * For now lets not share this publicly as it can become confusing. + * TODO: Use this to compare, for variants with changes. + */ + #document = new ObjectState(undefined); + + /** + * The document is the current state/draft version of the document. + */ + #draft = new ObjectState(undefined); + documentTypeKey = this.#draft.getObservablePart((data) => data?.contentTypeKey); + + variants = this.#draft.getObservablePart((data) => data?.variants || []); + urls = this.#draft.getObservablePart((data) => data?.urls || []); + templateKey = this.#draft.getObservablePart((data) => data?.templateKey || null); #documentTypes = new ArrayState([], (x) => x.key); documentTypes = this.#documentTypes.asObservable(); + mainDocumentType = this.#documentTypes.asObservable(); + + // Notice the DocumentTypePropertyTypeContainerModel is equivalent to PropertyTypeContainerViewModelBaseModel, making it easy to generalize. + #containers = new ArrayState([], (x) => x.key); + constructor(host: UmbControllerHostInterface) { super(host); this.#host = host; this.#documentRepository = new UmbDocumentRepository(this.#host); this.#documentTypeRepository = new UmbDocumentTypeRepository(this.#host); - //this.#dataTypeRepository = new UmbDataTypeRepository(this.#host); - new UmbObserverController(this._host, this.documentTypeKey, (key) => this.loadDocumentType(key)); + new UmbObserverController(this._host, this.documentTypeKey, (key) => this._loadDocumentType(key)); } async load(entityKey: string) { const { data } = await this.#documentRepository.requestByKey(entityKey); if (data) { this.#isNew = false; - this.#data.next(data); + this.#document.next(data); + this.#draft.next(data); } } @@ -53,10 +78,11 @@ export class UmbDocumentWorkspaceContext const { data } = await this.#documentRepository.createDetailsScaffold(parentKey); if (!data) return; this.#isNew = true; - this.#data.next(data); + this.#document.next(data); + this.#draft.next(data); } - async loadDocumentType(key?: string) { + private async _loadDocumentType(key?: string) { if (!key) return; const { data } = await this.#documentTypeRepository.requestByKey(key); @@ -65,43 +91,33 @@ export class UmbDocumentWorkspaceContext // Load inherited and composed types: await data?.compositions?.forEach(async (composition) => { if (composition.key) { - this.loadDocumentType(composition.key); + this._loadDocumentType(composition.key); } }); - new UmbObserverController(this._host, await this.#documentTypeRepository.byKey(key), (data) => { - if (data) { - this.#documentTypes.appendOne(data); - this.loadDataTypeOfDocumentType(data); + new UmbObserverController(this._host, await this.#documentTypeRepository.byKey(key), (docType) => { + if (docType) { + this.#documentTypes.appendOne(docType); + this._initDocumentTypeContainers(docType); + this._loadDocumentTypeCompositions(docType); } }); } - async loadDataTypeOfDocumentType(documentType?: DocumentTypeModel) { - if (!documentType) return; - - // Load inherited and composed types: - await documentType?.properties?.forEach(async (property) => { - if (property.dataTypeKey) { - this.loadDataType(property.dataTypeKey); - } + private async _loadDocumentTypeCompositions(documentType: DocumentTypeModel) { + documentType.compositions?.forEach((composition) => { + this._loadDocumentType(composition.key); }); } - async loadDataType(key?: string) { - if (!key) return; - - //const { data } = await this.#dataTypeRepository.requestDetails(key); - - /*new UmbObserverController(this._host, await this.#documentTypeRepository.byKey(key), (data) => { - if (data) { - this.#documentTypes.appendOne(data); - } - });*/ + private async _initDocumentTypeContainers(documentType: DocumentTypeModel) { + documentType.containers?.forEach((container) => { + this.#containers.appendOne(container); + }); } getData() { - return this.#data.getValue(); + return this.#draft.getValue() || {}; } /* @@ -111,7 +127,7 @@ export class UmbDocumentWorkspaceContext */ getEntityKey() { - return this.getData()?.key || ''; + return this.getData().key; } getEntityType() { @@ -119,65 +135,100 @@ export class UmbDocumentWorkspaceContext } setName(name: string, culture?: string | null, segment?: string | null) { - const variants = this.#data.getValue()?.variants || []; + const variants = this.#draft.getValue()?.variants || []; const newVariants = partialUpdateFrozenArray( variants, { name }, (v) => v.culture == culture && v.segment == segment ); - this.#data.update({ variants: newVariants }); - } - /* - getEntityType = this.#manager.getEntityType; - getUnique = this.#manager.getEntityKey; - getEntityKey = this.#manager.getEntityKey; - - */ - - /** - * Concept for Repository impl.: - - load(entityKey: string) { - this.#repository.load(entityKey).then((data) => { - this.#draft.next(data) - }) + this.#draft.update({ variants: newVariants }); } - create(parentKey: string | undefined) { - this.#repository.create(parentKey).then((data) => { - this.#draft.next(data) - }) - } - */ - - propertiesOf(culture: string | null, segment: string | null) { - return this.#data.getObservablePart((data) => + propertyValuesOf(culture: string | null, segment: string | null) { + return this.#draft.getObservablePart((data) => data?.properties?.filter((p) => (culture === p.culture || null) && (segment === p.segment || null)) ); } - propertyStructure() { - // TODO: handle composition of document types. - return this.#documentTypes.getObservablePart((data) => data[0]?.properties); + propertyValueOfAlias(propertyAlias: string, culture: string | null, segment: string | null) { + return this.#draft.getObservablePart((data) => + data?.properties?.find( + (p) => propertyAlias === p.alias && (culture === p.culture || null) && (segment === p.segment || null) + ) + ); } + hasPropertyStructuresOf(containerKey: string | null) { + return this.#documentTypes.getObservablePart((docTypes) => { + return ( + docTypes.find((docType) => { + return docType.properties?.find((property) => property.containerKey === containerKey); + }) !== undefined + ); + }); + } + rootPropertyStructures() { + return this.propertyStructuresOf(null); + } + propertyStructuresOf(containerKey: string | null) { + return this.#documentTypes.getObservablePart((docTypes) => { + const props: DocumentTypePropertyTypeModel[] = []; + docTypes.forEach((docType) => { + docType.properties?.forEach((property) => { + if (property.containerKey === containerKey) { + props.push(property); + } + }); + }); + return props; + }); + } + + // TODO: Check what of these methods I ended actually using: + + rootContainers(containerType: 'Group' | 'Tab') { + return this.#containers.getObservablePart((data) => { + return data.filter((x) => x.parentKey === null && x.type === containerType); + }); + } + + hasRootContainers(containerType: 'Group' | 'Tab') { + return this.#containers.getObservablePart((data) => { + return data.filter((x) => x.parentKey === null && x.type === containerType).length > 0; + }); + } + + containersOfParentKey( + parentKey: DocumentTypePropertyTypeContainerModel['parentKey'], + containerType: 'Group' | 'Tab' + ) { + return this.#containers.getObservablePart((data) => { + return data.filter((x) => x.parentKey === parentKey && x.type === containerType); + }); + } + + containersByNameAndType(name: string, containerType: 'Group' | 'Tab') { + return this.#containers.getObservablePart((data) => { + return data.filter((x) => x.name === name && x.type === containerType); + }); + } setPropertyValue(alias: string, value: unknown) { const entry = { alias: alias, value: value }; - const currentData = this.#data.value; + const currentData = this.#draft.value; if (currentData) { // TODO: make a partial update method for array of data, (idea/concept, use if this case is getting common) const newDataSet = appendToFrozenArray(currentData.properties || [], entry, (x) => x.alias); - this.#data.update({ properties: newDataSet }); + this.#draft.update({ properties: newDataSet }); } } async save() { - if (!this.#data.value) return; + if (!this.#draft.value) return; if (this.#isNew) { - await this.#documentRepository.createDetail(this.#data.value); + await this.#documentRepository.createDetail(this.#draft.value); } else { - await this.#documentRepository.saveDetail(this.#data.value); + await this.#documentRepository.saveDetail(this.#draft.value); } // If it went well, then its not new anymore?. this.#isNew = false; @@ -200,6 +251,6 @@ export class UmbDocumentWorkspaceContext */ public destroy(): void { - this.#data.complete(); + this.#draft.complete(); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/manifests.ts index 5ea6bbb231..85832b0aa3 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/manifests.ts @@ -25,7 +25,7 @@ const workspaceViews: Array = [ type: 'workspaceView', alias: 'Umb.WorkspaceView.Document.Edit', name: 'Document Workspace Edit View', - loader: () => import('./views/workspace-view-document-edit.element'), + loader: () => import('./views/document-workspace-view-edit.element'), weight: 200, meta: { workspaces: ['Umb.Workspace.Document'], diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-properties.element.ts new file mode 100644 index 0000000000..14903459cd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-properties.element.ts @@ -0,0 +1,149 @@ +import { html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, property, state } from 'lit/decorators.js'; +import { repeat } from 'lit/directives/repeat.js'; +import { UmbDocumentWorkspaceContext } from '../document-workspace.context'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { + DocumentPropertyModel, + DocumentTypePropertyTypeModel, + PropertyTypeContainerViewModelBaseModel, +} from '@umbraco-cms/backend-api'; + +@customElement('umb-document-workspace-view-edit-properties') +export class UmbDocumentWorkspaceViewEditPropertiesElement extends UmbLitElement { + static styles = [UUITextStyles]; + + private _containerName?: string; + + @property({ type: String, attribute: 'container-name', reflect: false }) + public get containerName(): string | undefined { + return this._containerName; + } + public set containerName(value: string | undefined) { + if (this._containerName === value) return; + this._containerName = value; + this._observeGroupContainers(); + } + + private _containerType?: 'Group' | 'Tab'; + + @property({ type: String, attribute: 'container-type', reflect: false }) + public get containerType(): 'Group' | 'Tab' | undefined { + return this._containerType; + } + public set containerType(value: 'Group' | 'Tab' | undefined) { + if (this._containerType === value) return; + this._containerType = value; + this._observeGroupContainers(); + } + + @state() + _groupContainers: Array = []; + + @state() + _propertyStructure: Array = []; + + // TODO: we should not get the values here, as its too hard to keep track of variants. + // Instead wrap each property in a variant aware component. + // As well we need to wrap the whole 'workspace' thing in a variant aware component. + // Remember that the other views should also differentiate, or be able to differentiate based on the selected variant. + // Also consider wrapping the variant data in a variant object. So we only need to parse one object every time we work with variants. + @state() + _propertyValueMap: Map = new Map(); + + private _workspaceContext?: UmbDocumentWorkspaceContext; + + constructor() { + super(); + + // TODO: Figure out how to get the magic string for the workspace context. + this.consumeContext('umbWorkspaceContext', (workspaceContext) => { + this._workspaceContext = workspaceContext; + this._observeGroupContainers(); + }); + } + + private _observeGroupContainers() { + if (!this._workspaceContext || !this._containerName || !this._containerType) return; + + // TODO: Should be no need to update this observable if its already there. + this.observe( + this._workspaceContext!.containersByNameAndType(this._containerName, this._containerType), + (groupContainers) => { + this._groupContainers = groupContainers || []; + groupContainers.forEach((group) => { + if (group.key) { + // Gather property aliases of this group, by group key. + this._observePropertyStructureOfGroup(group); + } + }); + }, + '_observeGroupContainers' + ); + } + + private _observePropertyStructureOfGroup(group: PropertyTypeContainerViewModelBaseModel) { + if (!this._workspaceContext || !group.key) return; + + // TODO: Should be no need to update this observable if its already there. + this.observe( + this._workspaceContext.propertyStructuresOf(group.key), + (properties) => { + // If this need to be able to remove properties, we need to clean out the ones of this group.key before inserting them: + this._propertyStructure = this._propertyStructure.filter((x) => x.containerKey !== group.key); + + properties?.forEach((property) => { + if (!this._propertyStructure.find((x) => x.alias === property.alias)) { + this._propertyStructure.push(property); + this._observePropertyValueOfAlias(property.alias!); + } + }); + + if (this._propertyStructure.length > 0) { + // TODO: Missing sort order? + //this._propertyStructure.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)); + } + }, + '_observePropertyStructureOfGroup' + group.key + ); + + // cache observable + } + + private _observePropertyValueOfAlias(propertyAlias: string) { + if (!this._workspaceContext || !propertyAlias) return; + + // TODO: Should be no need to update this observable if its already there. + this.observe( + this._workspaceContext.propertyValueOfAlias(propertyAlias, null, null), + (propertyValue) => { + if (propertyValue) { + this._propertyValueMap.set(propertyAlias, propertyValue); + } else { + this._propertyValueMap.delete(propertyAlias); + } + }, + '_observePropertyValueOfAlias' + propertyAlias + ); + } + + render() { + return repeat( + this._propertyStructure, + (property) => property.alias, + (property) => + html` ` + ); + } +} + +export default UmbDocumentWorkspaceViewEditPropertiesElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-document-workspace-view-edit-properties': UmbDocumentWorkspaceViewEditPropertiesElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-tab.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-tab.element.ts new file mode 100644 index 0000000000..b05f13291d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-tab.element.ts @@ -0,0 +1,175 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, property, state } from 'lit/decorators.js'; +import { repeat } from 'lit/directives/repeat.js'; +import { UmbDocumentWorkspaceContext } from '../document-workspace.context'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { PropertyTypeContainerViewModelBaseModel } from '@umbraco-cms/backend-api'; +import './document-workspace-view-edit-properties.element'; + +@customElement('umb-document-workspace-view-edit-tab') +export class UmbDocumentWorkspaceViewEditTabElement extends UmbLitElement { + static styles = [ + UUITextStyles, + css` + :host { + display: block; + margin: var(--uui-size-layout-1); + } + + uui-box { + margin-bottom: var(--uui-size-layout-1); + } + `, + ]; + + private _tabName?: string | undefined; + + @property({ type: String }) + public get tabName(): string | undefined { + return this._tabName; + } + public set tabName(value: string | undefined) { + const oldValue = this._tabName; + if (oldValue === value) return; + this._tabName = value; + this._observeTabContainers(); + this.requestUpdate('tabName', oldValue); + } + + private _noTabName = false; + + @property({ type: Boolean }) + public get noTabName(): boolean { + return this._noTabName; + } + public set noTabName(value: boolean) { + const oldValue = this._noTabName; + if (oldValue === value) return; + this._noTabName = value; + if (this._noTabName) { + this._tabName = undefined; + } + this._observeTabContainers(); + this.requestUpdate('noTabName', oldValue); + } + + @state() + _tabContainers: PropertyTypeContainerViewModelBaseModel[] = []; + + @state() + _hasTabProperties = false; + + @state() + _groups: Array = []; + + private _workspaceContext?: UmbDocumentWorkspaceContext; + + constructor() { + super(); + + // TODO: Figure out how to get the magic string for the workspace context. + this.consumeContext('umbWorkspaceContext', (workspaceContext) => { + this._workspaceContext = workspaceContext; + this._observeTabContainers(); + }); + } + + private _observeHasTabProperties() { + if (!this._workspaceContext) return; + + this._tabContainers.forEach((container) => { + this.observe( + this._workspaceContext!.hasPropertyStructuresOf(container.key!), + (hasTabProperties) => { + this._hasTabProperties = hasTabProperties; + }, + '_observeHasTabProperties_' + container.key + ); + }); + } + + private _observeTabContainers() { + if (!this._workspaceContext) return; + + if (this._tabName) { + this._groups = []; + this.observe( + this._workspaceContext.containersByNameAndType(this._tabName, 'Tab'), + (tabContainers) => { + this._tabContainers = tabContainers || []; + if (this._tabContainers.length > 0) { + this._observeHasTabProperties(); + this._observeGroups(); + } + }, + '_observeTabContainers' + ); + } else if (this._noTabName) { + this._groups = []; + this._observeRootGroups(); + } + } + + private _observeGroups() { + if (!this._workspaceContext || !this._tabName) return; + + this._tabContainers.forEach((container) => { + this.observe( + this._workspaceContext!.containersOfParentKey(container.key, 'Group'), + this._insertGroupContainers, + '_observeGroupsOf_' + container.key + ); + }); + } + + private _observeRootGroups() { + if (!this._workspaceContext || !this._noTabName) return; + + // This is where we potentially could observe root properties as well. + this.observe(this._workspaceContext!.rootContainers('Group'), this._insertGroupContainers, '_observeRootGroups'); + } + + private _insertGroupContainers = (groupContainers: PropertyTypeContainerViewModelBaseModel[]) => { + groupContainers.forEach((group) => { + if (group.name) { + if (!this._groups.find((x) => x.name === group.name)) { + this._groups.push(group); + this._groups.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)); + } + } + }); + }; + + render() { + // TODO: only show tab properties if there was any. We need some event? to tell us when the properties is empty. + return html` + ${this._hasTabProperties + ? html` + + + + ` + : ''} + ${repeat( + this._groups, + (group) => group.name, + (group) => html` + + ` + )} + `; + } +} + +export default UmbDocumentWorkspaceViewEditTabElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-document-workspace-view-edit-tab': UmbDocumentWorkspaceViewEditTabElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts new file mode 100644 index 0000000000..fee55c8d82 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts @@ -0,0 +1,178 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, state } from 'lit/decorators.js'; +import { repeat } from 'lit/directives/repeat.js'; +import { IRoute } from 'router-slot'; +import { UmbDocumentWorkspaceContext } from '../document-workspace.context'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { PropertyTypeContainerViewModelBaseModel } from '@umbraco-cms/backend-api'; +import { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/router'; + +@customElement('umb-document-workspace-view-edit') +export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement { + static styles = [ + UUITextStyles, + css` + :host { + display: block; + } + `, + ]; + + // TODO: get variant information via properties? + + //private _hasRootProperties = false; + private _hasRootGroups = false; + + @state() + private _routes: IRoute[] = []; + + @state() + _tabs: Array = []; + + @state() + private _routerPath?: string; + + @state() + private _activePath = ''; + + private _workspaceContext?: UmbDocumentWorkspaceContext; + + constructor() { + super(); + + // TODO: Figure out how to get the magic string for the workspace context. + this.consumeContext('umbWorkspaceContext', (workspaceContext) => { + this._workspaceContext = workspaceContext; + this._observeTabs(); + }); + } + + private _observeTabs() { + if (!this._workspaceContext) return; + + this.observe( + this._workspaceContext.rootContainers('Tab'), + (tabs) => { + tabs.forEach((tab) => { + // Only add each tab name once, as our containers merge on name: + if (!this._tabs.find((x) => x.name === tab.name || '')) { + this._tabs.push(tab); + } + }); + this._createRoutes(); + }, + '_observeTabs' + ); + + /* + Impleent this, when it becomes an option to have properties directly in the root of the document. + this.observe( + this._workspaceContext.rootPropertyStructures(), + (rootPropertyStructure) => { + console.log('rootPropertyStructure', rootPropertyStructure); + this._hasRootProperties = rootPropertyStructure.length > 0; + this._createRoutes(); + }, + '_observeTabs' + ); + */ + + this.observe( + this._workspaceContext.hasRootContainers('Group'), + (hasRootGroups) => { + this._hasRootGroups = hasRootGroups; + this._createRoutes(); + }, + '_observeTabs' + ); + } + + private _createRoutes() { + const routes: any[] = []; + + if (this._hasRootGroups) { + routes.push({ + path: 'root', + component: () => import('./document-workspace-view-edit-tab.element'), + setup: (component: Promise) => { + (component as any).noTabName = true; + }, + }); + } + + if (this._tabs.length > 0) { + this._tabs?.forEach((tab) => { + const tabName = tab.name; + routes.push({ + path: `tab/${encodeURI(tabName || '').toString()}`, + component: () => import('./document-workspace-view-edit-tab.element'), + setup: (component: Promise) => { + (component as any).tabName = tabName; + }, + }); + }); + } + + if (routes.length !== 0) { + routes.push({ + path: '', + redirectTo: routes[0]?.path, + }); + + routes.push({ + path: '**', + redirectTo: routes[0]?.path, + }); + } + + this._routes = routes; + } + + render() { + return html` + ${this._tabs.length > 1 + ? html` + ${this._hasRootGroups && this._tabs.length > 1 + ? html` + Content + ` + : ''} + ${repeat( + this._tabs, + (tab) => tab.name, + (tab) => { + const path = this._routerPath + '/tab/' + encodeURI(tab.name || ''); + return html`${tab.name}`; + } + )} + ` + : ''} + + { + this._routerPath = event.target.absoluteRouterPath; + }} + @change=${(event: UmbRouterSlotChangeEvent) => { + this._activePath = event.target.localActiveViewPath || ''; + }}> + + `; + } +} + +export default UmbDocumentWorkspaceViewEditElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-document-workspace-view-edit': UmbDocumentWorkspaceViewEditElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/workspace-view-document-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/workspace-view-document-edit.element.ts deleted file mode 100644 index c5a1cba82a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/workspace-view-document-edit.element.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { css, html } from 'lit'; -import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, state } from 'lit/decorators.js'; -import { repeat } from 'lit/directives/repeat.js'; -import { UmbDocumentWorkspaceContext } from '../document-workspace.context'; -import { UmbLitElement } from '@umbraco-cms/element'; -import { DocumentPropertyModel, DocumentTypePropertyTypeModel } from '@umbraco-cms/backend-api'; - -@customElement('umb-workspace-view-document-edit') -export class UmbWorkspaceViewDocumentEditElement extends UmbLitElement { - static styles = [ - UUITextStyles, - css` - :host { - display: block; - margin: var(--uui-size-layout-1); - } - `, - ]; - - @state() - _propertyData: DocumentPropertyModel[] = []; - - @state() - _propertyStructures: DocumentTypePropertyTypeModel[] = []; - - private _workspaceContext?: UmbDocumentWorkspaceContext; - - constructor() { - super(); - - // TODO: Figure out how to get the magic string for the workspace context. - this.consumeContext('umbWorkspaceContext', (workspaceContext) => { - this._workspaceContext = workspaceContext; - this._observeContent(); - }); - } - - private _observeContent() { - if (!this._workspaceContext) return; - - /* - TODO: Property-Context: This observer gets all changes, We need to fix this. it should be simpler. - An idea to optimize this would be for this to only care about layout, meaning to property data should be watched here. - As the properties could handle their own data on their own? - - Should use a Observable for example: this._workspaceContext.properties - */ - this.observe( - this._workspaceContext.propertiesOf(null, null), - (properties) => { - this._propertyData = properties || []; - //this._data = content?.data || []; - - /* - Maybe we should not give the value(Data), but the umb-content-property should get the context and observe its own data. - This would become a more specific Observer therefor better performance?.. Note to self: Debate with Mads how he sees this perspective. - */ - }, - 'observeWorkspaceContextData' - ); - this.observe( - this._workspaceContext.propertyStructure(), - (propertyStructure) => { - this._propertyStructures = propertyStructure || []; - }, - 'observeWorkspaceContextData' - ); - } - - render() { - return html` - - ${repeat( - this._propertyStructures, - (property) => property.alias, - (property) => - html` x.alias === property.alias)?.value}> ` - )} - - `; - } -} - -export default UmbWorkspaceViewDocumentEditElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-workspace-view-document-edit': UmbWorkspaceViewDocumentEditElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.element.ts index f9dc3fb899..6f68c9653d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.element.ts @@ -43,6 +43,8 @@ export class UmbWorkspaceContentElement extends UmbLitElement { @property() alias!: string; + // TODO: For variants and split view, we need to be able to repeat, either this element or make this element render multiple `umb-workspace-layout` + render() { return html` diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/document-type.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/document-type.data.ts index ba55f2165e..2b03f087b3 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/document-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/document-type.data.ts @@ -22,7 +22,7 @@ export const data: Array = [ properties: [ { key: '2', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'colorPicker', name: 'Color Picker', description: '', @@ -39,7 +39,7 @@ export const data: Array = [ }, { key: '3', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'contentPicker', name: 'Content Picker', description: '', @@ -56,7 +56,7 @@ export const data: Array = [ }, { key: '4', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'eyeDropper', name: 'Eye Dropper', description: '', @@ -73,7 +73,7 @@ export const data: Array = [ }, { key: '5', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'multiUrlPicker', name: 'Multi URL Picker', description: '', @@ -90,7 +90,7 @@ export const data: Array = [ }, { key: '6', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'multiNodeTreePicker', name: 'Multi Node Tree Picker', description: '', @@ -107,7 +107,7 @@ export const data: Array = [ }, { key: '7', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'datePicker', name: 'Date Picker', description: '', @@ -124,7 +124,7 @@ export const data: Array = [ }, { key: '8', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'email', name: 'Email', description: '', @@ -141,7 +141,7 @@ export const data: Array = [ }, { key: '9', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'textBox', name: 'Text Box', description: '', @@ -158,7 +158,7 @@ export const data: Array = [ }, { key: '19', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'dropdown', name: 'Dropdown', description: '', @@ -175,7 +175,7 @@ export const data: Array = [ }, { key: '11', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'textArea', name: 'Text Area', description: '', @@ -192,7 +192,7 @@ export const data: Array = [ }, { key: '12', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'slider', name: 'Slider', description: '', @@ -209,7 +209,7 @@ export const data: Array = [ }, { key: '13', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'toggle', name: 'Toggle', description: '', @@ -226,7 +226,7 @@ export const data: Array = [ }, { key: '14', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'tags', name: 'Tags', description: '', @@ -243,7 +243,7 @@ export const data: Array = [ }, { key: '15', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'markdownEditor', name: 'MarkdownEditor', description: '', @@ -260,7 +260,7 @@ export const data: Array = [ }, { key: '16', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'radioButtonList', name: 'Radio Button List', description: '', @@ -277,7 +277,7 @@ export const data: Array = [ }, { key: '17', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'checkboxList', name: 'Checkbox List', description: '', @@ -294,7 +294,7 @@ export const data: Array = [ }, { key: '18', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'blockList', name: 'Block List', description: '', @@ -311,7 +311,7 @@ export const data: Array = [ }, { key: '19', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'mediaPicker', name: 'Media Picker', description: '', @@ -328,7 +328,7 @@ export const data: Array = [ }, { key: '20', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'imageCropper', name: 'Image Cropper', description: '', @@ -345,7 +345,7 @@ export const data: Array = [ }, { key: '21', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'uploadField', name: 'Upload Field', description: '', @@ -362,7 +362,7 @@ export const data: Array = [ }, { key: '22', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'blockGrid', name: 'Block Grid', description: '', @@ -379,7 +379,7 @@ export const data: Array = [ }, { key: '23', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'blockGrid', name: 'Icon Picker', description: '', @@ -396,7 +396,7 @@ export const data: Array = [ }, { key: '24', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'numberRange', name: 'Number Range', description: '', @@ -413,7 +413,7 @@ export const data: Array = [ }, { key: '25', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'orderDirection', name: 'Order Direction', description: '', @@ -430,7 +430,7 @@ export const data: Array = [ }, { key: '26', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'overlaySize', name: 'Overlay Size', description: '', @@ -447,7 +447,7 @@ export const data: Array = [ }, { key: '27', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'label', name: 'Label', description: '', @@ -464,7 +464,7 @@ export const data: Array = [ }, { key: '28', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'integer', name: 'Integer', description: '', @@ -481,7 +481,7 @@ export const data: Array = [ }, { key: '29', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'decimal', name: 'Decimal', description: '', @@ -498,7 +498,7 @@ export const data: Array = [ }, { key: '30', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'memberPicker', name: 'Member Picker', description: '', @@ -515,7 +515,7 @@ export const data: Array = [ }, { key: '31', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'memberGroupPicker', name: 'Member Group Picker', description: '', @@ -532,7 +532,7 @@ export const data: Array = [ }, { key: '32', - containerKey: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + containerKey: 'all-properties-group-key', alias: 'userPicker', name: 'User Picker', description: '', @@ -550,7 +550,7 @@ export const data: Array = [ ], containers: [ { - key: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + key: 'all-properties-group-key', parentKey: null, name: 'Content', type: 'Group', @@ -652,7 +652,7 @@ export const data: Array = [ { key: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', parentKey: null, - name: 'Content', + name: 'Content-group', type: 'Group', sortOrder: 0, }, @@ -700,6 +700,102 @@ export const data: Array = [ keepLatestVersionPerDayForDays: null, }, }, + { + allowedTemplateKeys: ['916cfecc-3295-490c-a16d-c41fa9f72980'], + defaultTemplateKey: '916cfecc-3295-490c-a16d-c41fa9f72980', + key: '5035d7d9-0a63-415c-9e75-ee2cf931db92', + alias: 'masterPage', + name: 'Master Page', + description: null, + icon: 'icon-document', + allowedAsRoot: false, + variesByCulture: false, + variesBySegment: false, + isElement: false, + properties: [ + { + key: '5e5f7456-c751-4846-9f2b-47965cc96ec6', + containerKey: '6f281e5a-0242-4649-bd9e-d6bf87f92b41', + alias: 'masterText', + name: 'Master text', + description: null, + dataTypeKey: '0cc0eba1-9960-42c9-bf9b-60e150b429ae', + validation: { + mandatory: false, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + key: '6f281e5a-0242-4649-bd9e-d6bf87f92b41', + parentKey: null, + name: 'Master Tab', + type: 'Tab', + sortOrder: 0, + }, + ], + allowedContentTypes: [], + compositions: [], + cleanup: { + preventCleanup: false, + keepAllVersionsNewerThanDays: null, + keepLatestVersionPerDayForDays: null, + }, + }, + { + allowedTemplateKeys: [], + defaultTemplateKey: null, + key: '8f68ba66-6fb2-4778-83b8-6ab4ca3a7c5d', + alias: 'baseElementType', + name: 'Base Element Type', + description: null, + icon: 'icon-science', + allowedAsRoot: false, + variesByCulture: false, + variesBySegment: false, + isElement: true, + properties: [ + { + key: 'b92de6ac-1a22-4a45-a481-b6cae1cccbbf', + containerKey: '1e845ca8-1e3e-4b03-be1d-0b4149ce2129', + alias: 'pageTitle', + name: 'Page title', + description: null, + dataTypeKey: '0cc0eba1-9960-42c9-bf9b-60e150b429ae', + validation: { + mandatory: false, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + key: '1e845ca8-1e3e-4b03-be1d-0b4149ce2129', + parentKey: null, + name: 'Content-group', + type: 'Group', + sortOrder: 0, + }, + ], + allowedContentTypes: [], + compositions: [], + cleanup: { + preventCleanup: false, + keepAllVersionsNewerThanDays: null, + keepLatestVersionPerDayForDays: null, + }, + }, ]; export const treeData: Array = [ diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/document.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/document.data.ts index 4fbac76d2a..7afb407c48 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/document.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/document.data.ts @@ -230,10 +230,196 @@ export const data: Array = [ properties: [ // TODO: is 'properties' the correct name for this? The property comes from the doc type, and this only holds the values. { + alias: 'email', culture: null, segment: null, + value: null, + }, + { + alias: 'colorPicker', + culture: null, + segment: null, + value: null, + }, + { + alias: 'contentPicker', + culture: null, + segment: null, + value: null, + }, + { + alias: 'eyeDropper', + culture: null, + segment: null, + value: null, + }, + { + alias: 'multiUrlPicker', + culture: null, + segment: null, + value: null, + }, + { + alias: 'multiNodeTreePicker', + culture: null, + segment: null, + value: null, + }, + { + alias: 'datePicker', + culture: null, + segment: null, + value: null, + }, + { alias: 'email', - value: 'mail@umbraco.com', + culture: null, + segment: null, + value: null, + }, + { + alias: 'textBox', + culture: null, + segment: null, + value: null, + }, + { + alias: 'dropdown', + culture: null, + segment: null, + value: null, + }, + { + alias: 'textArea', + culture: null, + segment: null, + value: null, + }, + { + alias: 'slider', + culture: null, + segment: null, + value: null, + }, + { + alias: 'toggle', + culture: null, + segment: null, + value: null, + }, + { + alias: 'tags', + culture: null, + segment: null, + value: null, + }, + { + alias: 'markdownEditor', + culture: null, + segment: null, + value: null, + }, + { + alias: 'radioButtonList', + culture: null, + segment: null, + value: null, + }, + { + alias: 'checkboxList', + culture: null, + segment: null, + value: null, + }, + { + alias: 'blockList', + culture: null, + segment: null, + value: null, + }, + { + alias: 'mediaPicker', + culture: null, + segment: null, + value: null, + }, + { + alias: 'imageCropper', + culture: null, + segment: null, + value: null, + }, + { + alias: 'uploadField', + culture: null, + segment: null, + value: null, + }, + { + alias: 'blockGrid', + culture: null, + segment: null, + value: null, + }, + { + alias: 'blockGrid', + culture: null, + segment: null, + value: null, + }, + { + alias: 'numberRange', + culture: null, + segment: null, + value: null, + }, + { + alias: 'orderDirection', + culture: null, + segment: null, + value: null, + }, + { + alias: 'overlaySize', + culture: null, + segment: null, + value: null, + }, + { + alias: 'label', + culture: null, + segment: null, + value: null, + }, + { + alias: 'integer', + culture: null, + segment: null, + value: null, + }, + { + alias: 'decimal', + culture: null, + segment: null, + value: null, + }, + { + alias: 'memberPicker', + culture: null, + segment: null, + value: null, + }, + { + alias: 'memberGroupPicker', + culture: null, + segment: null, + value: null, + }, + { + alias: 'userPicker', + culture: null, + segment: null, + value: null, }, ], variants: [ From 57d34b5636541ec77237b75e7396c705c8d1fce5 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 15:26:16 +0100 Subject: [PATCH 089/116] handle fallback language change event --- .../edit-language-workspace-view.element.ts | 25 ++++++++----------- .../input-language-picker.element.ts | 3 ++- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 441bc067a6..f23066ab39 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -9,6 +9,7 @@ import { UmbLitElement } from '@umbraco-cms/element'; import { LanguageModel } from '@umbraco-cms/backend-api'; import { UmbChangeEvent } from 'src/core/events'; import UmbInputCultureSelectElement from 'src/backoffice/shared/components/input-culture-select/input-culture-select.element'; +import UmbInputLanguagePickerElement from 'src/backoffice/shared/components/input-language-picker/input-language-picker.element'; @customElement('umb-edit-language-workspace-view') export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { @@ -111,23 +112,14 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { } } - #handleFallbackChange(event: UUIComboboxEvent) { - if (event instanceof UUIComboboxEvent) { - const target = event.composedPath()[0] as UUIComboboxElement; - this.#languageWorkspaceContext?.setFallbackCulture(target.value.toString()); + #handleFallbackChange(event: UmbChangeEvent) { + if (event instanceof UmbChangeEvent) { + const target = event.target as UmbInputLanguagePickerElement; + const selectedLanguageIsoCode = target.selectedIsoCodes?.[0]; + this.#languageWorkspaceContext?.setFallbackCulture(selectedLanguageIsoCode); } } - get #fallbackLanguages() { - return this._languages.filter((language) => { - return language.isoCode !== this.language?.isoCode; - }); - } - - get #fallbackLanguage() { - return this.#fallbackLanguages.find((language) => language.isoCode === this.language?.fallbackIsoCode); - } - #renderCultureWarning() { if (!this._startData?.isoCode || this._startData?.isoCode === this.language?.isoCode) return nothing; @@ -188,7 +180,10 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { - +
    `; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts index 8ab5c68342..a2d965ca9b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts @@ -8,6 +8,7 @@ import { UmbLitElement } from '@umbraco-cms/element'; import type { LanguageModel } from '@umbraco-cms/backend-api'; import type { UmbObserverController } from '@umbraco-cms/observable-api'; import { UmbLanguageRepository } from 'src/backoffice/settings/languages/repository/language.repository'; +import { UmbChangeEvent } from 'src/core/events'; @customElement('umb-input-language-picker') export class UmbInputLanguagePickerElement extends FormControlMixin(UmbLitElement) { @@ -143,7 +144,7 @@ export class UmbInputLanguagePickerElement extends FormControlMixin(UmbLitElemen private _setSelection(newSelection: Array) { this.selectedIsoCodes = newSelection; - this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); + this.dispatchEvent(new UmbChangeEvent()); } render() { From 441e434c8c32f4a95836b2a8aeb8d138803a3fbb Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 15:29:09 +0100 Subject: [PATCH 090/116] set value for fallback language on language picker input --- .../edit-language-workspace-view.element.ts | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index f23066ab39..5ff159443a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -47,8 +47,8 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { `, ]; - @property() - language?: LanguageModel; + @state() + _language?: LanguageModel; @state() private _languages: LanguageModel[] = []; @@ -65,7 +65,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { this.#languageWorkspaceContext = instance; this.observe(this.#languageWorkspaceContext.data, (language) => { - this.language = language; + this._language = language; }); }); } @@ -81,7 +81,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { // Provides a way better UX //TODO: Maybe the combobox should implement something similar? const resetFunction = () => { - target.value = this.language?.isoCode ?? ''; + target.value = this._language?.isoCode ?? ''; }; target.addEventListener('close', resetFunction, { once: true }); @@ -92,7 +92,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { this.#languageWorkspaceContext?.setCulture(isoCode); // If the language name is not set, we set it to the name of the selected language. - if (!this.language?.name && cultureName) { + if (!this._language?.name && cultureName) { this.#languageWorkspaceContext?.setName(cultureName); } } @@ -121,7 +121,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { } #renderCultureWarning() { - if (!this._startData?.isoCode || this._startData?.isoCode === this.language?.isoCode) return nothing; + if (!this._startData?.isoCode || this._startData?.isoCode === this._language?.isoCode) return nothing; return html`
    Changing the culture for a language may be an expensive operation and will result in the content cache and indexes @@ -130,7 +130,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { } #renderDefaultLanguageWarning() { - if (this._startData?.isDefault || this.language?.isDefault !== true) return nothing; + if (this._startData?.isDefault || this._language?.isDefault !== true) return nothing; return html`
    Switching default language may result in default content missing. @@ -138,28 +138,28 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { } render() { - if (!this.language) return nothing; + if (!this._language) return nothing; return html`
    ${this.#renderCultureWarning()}
    -
    ${this.language.isoCode}
    +
    ${this._language.isoCode}
    Default language @@ -168,7 +168,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { ${this.#renderDefaultLanguageWarning()}
    - +
    Mandatory language
    Properties on this language have to be filled out before the node can be published.
    @@ -181,6 +181,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { label="Fallback language" description="To allow multi-lingual content to fall back to another language if not present in the requested language, select it here."> From 36b09ecc7d8e60e1b6769b165844fc57c2fa6079 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 15:32:39 +0100 Subject: [PATCH 091/116] remove unused --- .../views/edit/edit-language-workspace-view.element.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 5ff159443a..159cf58e32 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -1,8 +1,7 @@ -import { UUIBooleanInputEvent, UUIComboboxElement, UUIComboboxEvent, UUIToggleElement } from '@umbraco-ui/uui'; +import { UUIBooleanInputEvent, UUIToggleElement } from '@umbraco-ui/uui'; import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing } from 'lit'; -import { repeat } from 'lit/directives/repeat.js'; -import { customElement, property, state } from 'lit/decorators.js'; +import { customElement, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { UmbLanguageWorkspaceContext } from '../../language-workspace.context'; import { UmbLitElement } from '@umbraco-cms/element'; @@ -50,9 +49,6 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { @state() _language?: LanguageModel; - @state() - private _languages: LanguageModel[] = []; - @state() private _startData: LanguageModel | null = null; From bbccf53b3ff117b28e905ce8c742586d84ffc3fc Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 19:43:09 +0100 Subject: [PATCH 092/116] add app language select --- .../src/backoffice/backoffice.element.ts | 4 +- .../languages/app-language-select.element.ts | 130 ++++++++++++++++++ .../languages/app-language.context.ts | 50 +++++++ .../repository/language.repository.ts | 39 ++++-- .../section-sidebar.element.ts | 2 +- .../components/section/section.element.ts | 42 +++--- 6 files changed, 230 insertions(+), 37 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language.context.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts index 53474073fb..69e02e26c2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -40,6 +40,8 @@ import { UmbTemplateDetailStore } from './templating/templates/workspace/data/te import { UmbThemeContext } from './themes/theme.context'; import { UmbLanguageStore } from './settings/languages/repository/language.store'; import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { UMB_APP_LANGUAGE_CONTEXT_TOKEN, UmbAppLanguageContext } from './settings/languages/app-language.context'; import '@umbraco-cms/router'; @@ -54,7 +56,6 @@ import './packages'; import './search'; import './templating'; import './shared'; -import { UmbLitElement } from '@umbraco-cms/element'; @defineElement('umb-backoffice') export class UmbBackofficeElement extends UmbLitElement { @@ -108,6 +109,7 @@ export class UmbBackofficeElement extends UmbLitElement { new UmbTemplateDetailStore(this); new UmbLanguageStore(this); + this.provideContext(UMB_APP_LANGUAGE_CONTEXT_TOKEN, new UmbAppLanguageContext(this)); this.provideContext(UMB_BACKOFFICE_CONTEXT_TOKEN, new UmbBackofficeContext()); this.provideContext(UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, new UmbCurrentUserHistoryStore()); new UmbThemeContext(this); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts new file mode 100644 index 0000000000..de7e8b6406 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts @@ -0,0 +1,130 @@ +import { UUITextStyles } from '@umbraco-ui/uui-css'; +import { css, html } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { repeat } from 'lit/directives/repeat.js'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; +import { UUIMenuItemEvent } from '@umbraco-ui/uui'; +import { UmbLanguageRepository } from './repository/language.repository'; +import { UMB_APP_LANGUAGE_CONTEXT_TOKEN, UmbAppLanguageContext } from './app-language.context'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { LanguageModel } from '@umbraco-cms/backend-api'; + +@customElement('umb-app-language-select') +export class UmbAppLanguageSelectElement extends UmbLitElement { + static styles = [ + UUITextStyles, + css` + :host { + display: block; + position: relative; + z-index: 10; + } + + #selected { + display: block; + width: 100%; + text-align: left; + background: none; + border: none; + padding: 20px 10px; + font-size: 1rem; + font-weight: bold; + } + `, + ]; + + @state() + private _languages: Array = []; + + @state() + private _appLanguage?: LanguageModel; + + @state() + private _isOpen = false; + + #repository = new UmbLanguageRepository(this); + #appLanguageContext?: UmbAppLanguageContext; + #languagesObserver?: any; + + constructor() { + super(); + + this.consumeContext(UMB_APP_LANGUAGE_CONTEXT_TOKEN, (instance) => { + this.#appLanguageContext = instance; + this.#observeAppLanguage(); + }); + } + + async #observeAppLanguage() { + if (!this.#appLanguageContext) return; + + this.observe(this.#appLanguageContext.appLanguage, (isoCode) => { + this._appLanguage = isoCode; + }); + } + + async #observeLanguages() { + const { asObservable } = await this.#repository.requestLanguages(); + + this.#languagesObserver = this.observe(asObservable(), (languages) => { + this._languages = languages; + }); + } + + #onClick() { + this.#toggleDropdown(); + } + + #onClose() { + this.#closeDropdown(); + } + + #toggleDropdown() { + this._isOpen = !this._isOpen; + + // first start observing the languages when the dropdown is opened + if (this._isOpen && !this.#languagesObserver) { + this.#observeLanguages(); + } + } + + #closeDropdown() { + this._isOpen = false; + } + + #onLabelClick(event: UUIMenuItemEvent) { + const menuItem = event.target; + const isoCode = menuItem.dataset.isoCode; + + // TODO: handle error + if (!isoCode) return; + + this.#appLanguageContext?.setLanguage(isoCode); + this._isOpen = false; + } + + render() { + return html` + +
    + ${repeat( + this._languages, + (language) => language.isoCode, + (language) => + html` + + ` + )} +
    +
    `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-app-language-select': UmbAppLanguageSelectElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language.context.ts new file mode 100644 index 0000000000..603e2e4955 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language.context.ts @@ -0,0 +1,50 @@ +import { UmbLanguageRepository } from './repository/language.repository'; +import { ObjectState, UmbObserverController } from '@umbraco-cms/observable-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { LanguageModel } from '@umbraco-cms/backend-api'; + +export class UmbAppLanguageContext { + #host: UmbControllerHostInterface; + #languageRepository: UmbLanguageRepository; + + #languages: Array = []; + + #appLanguage = new ObjectState(undefined); + appLanguage = this.#appLanguage.asObservable(); + + constructor(host: UmbControllerHostInterface) { + this.#host = host; + this.#languageRepository = new UmbLanguageRepository(this.#host); + this.#observeLanguages(); + } + + setLanguage(isoCode: string) { + const language = this.#languages.find((x) => x.isoCode === isoCode); + this.#appLanguage.update(language); + } + + async #observeLanguages() { + const { asObservable } = await this.#languageRepository.requestLanguages(); + + new UmbObserverController(this.#host, asObservable(), (languages) => { + this.#languages = languages; + + // If the app language is not set, set it to the default language + if (!this.#appLanguage.getValue()) { + this.#initAppLanguage(); + } + }); + } + + #initAppLanguage() { + const defaultLanguage = this.#languages.find((x) => x.isDefault); + // TODO: do we always have a default language? + // do we always get the default language on the first request, or could it be on page 2? + // in that case do we then need an endpoint to get the default language? + if (!defaultLanguage?.isoCode) return; + this.setLanguage(defaultLanguage.isoCode); + } +} + +export const UMB_APP_LANGUAGE_CONTEXT_TOKEN = new UmbContextToken(UmbAppLanguageContext.name); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts index e172c4e320..ca20ff005d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts @@ -10,7 +10,7 @@ export class UmbLanguageRepository { #host: UmbControllerHostInterface; - #detailDataSource: UmbLanguageServerDataSource; + #dataSource: UmbLanguageServerDataSource; #languageStore?: UmbLanguageStore; #notificationService?: UmbNotificationService; @@ -18,8 +18,7 @@ export class UmbLanguageRepository { constructor(host: UmbControllerHostInterface) { this.#host = host; - // TODO: figure out how spin up get the correct data source - this.#detailDataSource = new UmbLanguageServerDataSource(this.#host); + this.#dataSource = new UmbLanguageServerDataSource(this.#host); this.#init = Promise.all([ new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { @@ -41,7 +40,7 @@ export class UmbLanguageRepository { return { error }; } - return this.#detailDataSource.get(isoCode); + return this.#dataSource.get(isoCode); } // TODO: maybe this should be renamed to something more generic. @@ -49,7 +48,7 @@ export class UmbLanguageRepository { async requestLanguages({ skip, take } = { skip: 0, take: 1000 }) { await this.#init; - const { data, error } = await this.#detailDataSource.getCollection({ skip, take }); + const { data, error } = await this.#dataSource.getCollection({ skip, take }); if (data) { // TODO: allow to append an array of items to the store @@ -79,16 +78,16 @@ export class UmbLanguageRepository { * Creates a new Language scaffold * @param * @return {*} - * @memberof UmbLanguageServerDataSource + * @memberof UmbLanguageRepository */ async createScaffold() { - return this.#detailDataSource.createScaffold(); + return this.#dataSource.createScaffold(); } async create(language: LanguageModel) { await this.#init; - const { data, error } = await this.#detailDataSource.update(language); + const { data, error } = await this.#dataSource.update(language); if (data) { this.#languageStore?.append(data); @@ -99,10 +98,16 @@ export class UmbLanguageRepository { return { data, error }; } + /** + * Saves a language + * @param {LanguageModel} language + * @return {*} + * @memberof UmbLanguageRepository + */ async save(language: LanguageModel) { await this.#init; - const { data, error } = await this.#detailDataSource.update(language); + const { data, error } = await this.#dataSource.update(language); if (data) { const notification = { data: { message: `Language saved` } }; @@ -113,22 +118,28 @@ export class UmbLanguageRepository { return { data, error }; } - async delete(key: string) { + /** + * Deletes a language + * @param {string} isoCode + * @return {*} + * @memberof UmbLanguageRepository + */ + async delete(isoCode: string) { await this.#init; - if (!key) { - const error: ProblemDetailsModel = { title: 'Language key is missing' }; + if (!isoCode) { + const error: ProblemDetailsModel = { title: 'Language iso code is missing' }; return { error }; } - const { error } = await this.#detailDataSource.delete(key); + const { error } = await this.#dataSource.delete(isoCode); if (!error) { const notification = { data: { message: `Language deleted` } }; this.#notificationService?.peek('positive', notification); } - this.#languageStore?.remove([key]); + this.#languageStore?.remove([isoCode]); return { error }; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts index ca2411549a..23bd2fce7b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts @@ -21,7 +21,7 @@ export class UmbSectionSidebarElement extends UmbLitElement { font-weight: 500; display: flex; flex-direction: column; - z-index:10; + z-index: 10; } h3 { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts index 6c63ea29ae..cd16140545 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts @@ -11,6 +11,8 @@ import { UmbLitElement } from '@umbraco-cms/element'; import './section-sidebar-menu/section-sidebar-menu.element.ts'; import './section-views/section-views.element.ts'; +import '../../../settings/languages/app-language-select.element.ts'; + import { UmbRouterSlotChangeEvent } from '@umbraco-cms/router'; @customElement('umb-section') @@ -89,7 +91,6 @@ export class UmbSectionElement extends UmbLitElement { } private _createMenuRoutes() { - // TODO: find a way to make this reuseable across: const workspaceRoutes = this._workspaces?.map((workspace: ManifestWorkspace) => { return [ @@ -140,35 +141,30 @@ export class UmbSectionElement extends UmbLitElement { ]; } - - private _observeSection() { if (!this._sectionContext) return; - this.observe( - this._sectionContext.alias, (alias) => { - this._sectionAlias = alias; - this._observeViews(); - } - ); + this.observe(this._sectionContext.alias, (alias) => { + this._sectionAlias = alias; + this._observeViews(); + }); } private _observeViews() { - this.observe(umbExtensionsRegistry?.extensionsOfType('sectionView'), (views) => { - const sectionViews = views.filter((view) => { - return this._sectionAlias ? view.meta.sections.includes(this._sectionAlias) : false - }).sort((a, b) => b.meta.weight - a.meta.weight); - if(sectionViews.length > 0) { - this._views = sectionViews; - this._createViewRoutes(); - } + const sectionViews = views + .filter((view) => { + return this._sectionAlias ? view.meta.sections.includes(this._sectionAlias) : false; + }) + .sort((a, b) => b.meta.weight - a.meta.weight); + if (sectionViews.length > 0) { + this._views = sectionViews; + this._createViewRoutes(); } - ); + }); } private _createViewRoutes() { - this._routes = this._views?.map((view) => { return { @@ -190,13 +186,14 @@ export class UmbSectionElement extends UmbLitElement { const view = this._views?.find((view) => 'view/' + view.meta.pathname === currentPath); if (!view) return; this._sectionContext?.setActiveView(view); - } + }; render() { return html` ${this._menuItems && this._menuItems.length > 0 ? html` + ` @@ -204,7 +201,10 @@ export class UmbSectionElement extends UmbLitElement { ${this._views && this._views.length > 0 ? html`` : nothing} ${this._routes && this._routes.length > 0 - ? html`` + ? html`` : nothing} From 2e0e164281a0f22652f166ef333710b035776a70 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 19:49:27 +0100 Subject: [PATCH 093/116] don't set width on dropdown --- .../backoffice/shared/components/dropdown/dropdown.element.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/dropdown/dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/dropdown/dropdown.element.ts index 8f6ce7868a..ab21ee4f5e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/dropdown/dropdown.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/dropdown/dropdown.element.ts @@ -22,7 +22,6 @@ export class UmbDropdownElement extends UmbLitElement { height: 100%; box-sizing: border-box; box-shadow: var(--uui-shadow-depth-3); - width: 500px; } `, ]; From d0bc397d34756f4457eb56d788695a3b199d0492 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 19:49:44 +0100 Subject: [PATCH 094/116] make scroll container full height --- .../section/section-sidebar/section-sidebar.element.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts index 23bd2fce7b..74474bdf88 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts @@ -27,6 +27,10 @@ export class UmbSectionSidebarElement extends UmbLitElement { h3 { padding: var(--uui-size-4) var(--uui-size-8); } + + #scroll-container { + height: 100%; + } `, ]; @@ -64,7 +68,7 @@ export class UmbSectionSidebarElement extends UmbLitElement { render() { return html` - +

    ${this._sectionLabel}

    From d0019ed126d25734585718c7cf4a3d0fbd624d5a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 19:53:48 +0100 Subject: [PATCH 095/116] adjust padding and add border --- .../settings/languages/app-language-select.element.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts index de7e8b6406..f4a0851ad0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts @@ -26,7 +26,8 @@ export class UmbAppLanguageSelectElement extends UmbLitElement { text-align: left; background: none; border: none; - padding: 20px 10px; + padding: var(--uui-size-4) var(--uui-size-8); + border-bottom: 1px solid var(--uui-color-border); font-size: 1rem; font-weight: bold; } From 2892608999ceb2a40313275ac04fec9ee03172f9 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 20:11:34 +0100 Subject: [PATCH 096/116] temp move sidebar headings + set fixed height on language toggle --- .../languages/app-language-select.element.ts | 7 ++-- .../section-sidebar.element.ts | 35 +------------------ .../components/section/section.element.ts | 26 ++++++++++++-- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts index f4a0851ad0..b4a981056b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts @@ -20,13 +20,14 @@ export class UmbAppLanguageSelectElement extends UmbLitElement { z-index: 10; } - #selected { + #toggle { display: block; width: 100%; text-align: left; background: none; border: none; - padding: var(--uui-size-4) var(--uui-size-8); + height: 70px; + padding: 0 var(--uui-size-8); border-bottom: 1px solid var(--uui-color-border); font-size: 1rem; font-weight: bold; @@ -106,7 +107,7 @@ export class UmbAppLanguageSelectElement extends UmbLitElement { render() { return html` - +
    ${repeat( this._languages, diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts index 74474bdf88..bf892f19f8 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts @@ -1,7 +1,6 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html } from 'lit'; -import { customElement, state } from 'lit/decorators.js'; -import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from '../section.context'; +import { customElement } from 'lit/decorators.js'; import '../../tree/context-menu/tree-context-menu.service'; import '../section-sidebar-context-menu/section-sidebar-context-menu.element'; @@ -24,55 +23,23 @@ export class UmbSectionSidebarElement extends UmbLitElement { z-index: 10; } - h3 { - padding: var(--uui-size-4) var(--uui-size-8); - } - #scroll-container { height: 100%; } `, ]; - @state() - private _sectionLabel = ''; - - @state() - private _sectionPathname = ''; - - private _sectionContext?: UmbSectionContext; #sectionSidebarContext = new UmbSectionSidebarContext(this); constructor() { super(); - - this.consumeContext(UMB_SECTION_CONTEXT_TOKEN, (sectionContext) => { - this._sectionContext = sectionContext; - this._observeSectionContext(); - }); - this.provideContext(UMB_SECTION_SIDEBAR_CONTEXT_TOKEN, this.#sectionSidebarContext); } - private _observeSectionContext() { - if (!this._sectionContext) return; - - this.observe(this._sectionContext.pathname, (pathname) => { - this._sectionPathname = pathname || ''; - }); - this.observe(this._sectionContext.label, (label) => { - this._sectionLabel = label || ''; - }); - } - render() { return html` - -

    ${this._sectionLabel}

    -
    -
    diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts index cd16140545..e5d2999d82 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts @@ -30,6 +30,10 @@ export class UmbSectionElement extends UmbLitElement { overflow: auto; height: 100%; } + + h3 { + padding: var(--uui-size-4) var(--uui-size-8); + } `, ]; @@ -40,11 +44,16 @@ export class UmbSectionElement extends UmbLitElement { @state() private _menuItems?: Array; - private _workspaces?: Array; - @state() private _views?: Array; + @state() + private _sectionLabel = ''; + + @state() + private _sectionPathname = ''; + + private _workspaces?: Array; private _sectionContext?: UmbSectionContext; private _sectionAlias?: string; @@ -148,6 +157,14 @@ export class UmbSectionElement extends UmbLitElement { this._sectionAlias = alias; this._observeViews(); }); + + this.observe(this._sectionContext.pathname, (pathname) => { + this._sectionPathname = pathname || ''; + }); + + this.observe(this._sectionContext.label, (label) => { + this._sectionLabel = label || ''; + }); } private _observeViews() { @@ -194,6 +211,11 @@ export class UmbSectionElement extends UmbLitElement { ? html` + + + +

    ${this._sectionLabel}

    +
    ` From 55c15e16e30b7e93a759f55d6e8178b2570a99bc Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 20:14:01 +0100 Subject: [PATCH 097/116] set fixed body header height --- .../shared/components/body-layout/body-layout.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.element.ts index 1f12bd5873..0770f98a60 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/body-layout/body-layout.element.ts @@ -20,7 +20,7 @@ export class UmbBodyLayout extends LitElement { align-items: center; justify-content: space-between; width: 100%; - min-height: 60px; + height: 70px; background-color: var(--uui-color-surface); border-bottom: 1px solid var(--uui-color-border); box-sizing: border-box; From 7ccc22859c2f86a28f7482c372c8c6bb363bf818 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 20:15:28 +0100 Subject: [PATCH 098/116] only show scroll bars if sidebar content is scrollable --- .../section/section-sidebar/section-sidebar.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts index bf892f19f8..d937a7204d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts @@ -25,6 +25,7 @@ export class UmbSectionSidebarElement extends UmbLitElement { #scroll-container { height: 100%; + overflow-y: auto; } `, ]; From 054cad0bf12d458d73193d2a281e2beead7ceaac Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 20:18:29 +0100 Subject: [PATCH 099/116] align height --- .../section-dashboards.element.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts index a3d8f6a3d2..61f4bf1fd3 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts @@ -25,11 +25,12 @@ export class UmbSectionDashboardsElement extends UmbLitElement { background-color: var(--uui-color-surface); height: 70px; border-bottom: 1px solid var(--uui-color-border); + box-sizing: border-box; } #scroll-container { - flex:1; - position:relative; + flex: 1; + position: relative; } #router-slot { @@ -45,10 +46,10 @@ export class UmbSectionDashboardsElement extends UmbLitElement { width: 100%; min-height: 60px; box-sizing: border-box; - margin:0; - padding:0 var(--uui-size-5); - background-color:var(--uui-color-surface); - border-bottom:1px solid var(--uui-color-border); + margin: 0; + padding: 0 var(--uui-size-5); + background-color: var(--uui-color-surface); + border-bottom: 1px solid var(--uui-color-border); } `, ]; @@ -171,8 +172,7 @@ export class UmbSectionDashboardsElement extends UmbLitElement { }} @change=${(event: UmbRouterSlotChangeEvent) => { this._activePath = event.target.localActiveViewPath; - }} - > + }}> `; } From 9cf99ff5caec249ae22d3dbbd99a2bc2f4f0879d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 21:47:15 +0100 Subject: [PATCH 100/116] create full scaffold --- .../languages/repository/sources/language.server.data.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts index 6cc694ba15..cca5af2960 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts @@ -48,6 +48,10 @@ export class UmbLanguageServerDataSource implements UmbLanguageServerDataSource */ async createScaffold() { const data: LanguageModel = { + name: '', + isDefault: false, + isMandatory: false, + fallbackIsoCode: '', isoCode: '', }; From 5fb5b5a741bb0337b364a62ff1590dfc6d3f5704 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 21:47:30 +0100 Subject: [PATCH 101/116] align handlers with end points --- .../src/core/mocks/domains/language.handlers.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/language.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/language.handlers.ts index 0744df0db5..a4399f0cf8 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/language.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/language.handlers.ts @@ -39,9 +39,9 @@ export const handlers = [ data.id = umbLanguagesData.getAll().length + 1; data.key = uuidv4(); - const saved = umbLanguagesData.save([data]); + umbLanguagesData.save([data]); - return res(ctx.status(200), ctx.json(saved[0])); + return res(ctx.status(201)); }), rest.put(umbracoPath('/language/:key'), async (req, res, ctx) => { @@ -49,9 +49,9 @@ export const handlers = [ if (!data) return; - const saved = umbLanguagesData.save([data]); + umbLanguagesData.save([data]); - return res(ctx.status(200), ctx.json(saved[0])); + return res(ctx.status(200)); }), rest.delete(umbracoPath('/language/:key'), async (req, res, ctx) => { From 4f7d3e2419c01f9cbff9860d3a74254be772510e Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 21:47:54 +0100 Subject: [PATCH 102/116] align server data source with end points --- .../repository/language.repository.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts index ca20ff005d..9fa8683457 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts @@ -87,15 +87,15 @@ export class UmbLanguageRepository { async create(language: LanguageModel) { await this.#init; - const { data, error } = await this.#dataSource.update(language); + const { error } = await this.#dataSource.insert(language); - if (data) { - this.#languageStore?.append(data); + if (!error) { + this.#languageStore?.append(language); const notification = { data: { message: `Language created` } }; this.#notificationService?.peek('positive', notification); } - return { data, error }; + return { error }; } /** @@ -107,15 +107,15 @@ export class UmbLanguageRepository { async save(language: LanguageModel) { await this.#init; - const { data, error } = await this.#dataSource.update(language); + const { error } = await this.#dataSource.update(language); - if (data) { + if (!error) { const notification = { data: { message: `Language saved` } }; this.#notificationService?.peek('positive', notification); - this.#languageStore?.append(data); + this.#languageStore?.append(language); } - return { data, error }; + return { error }; } /** @@ -135,12 +135,11 @@ export class UmbLanguageRepository { const { error } = await this.#dataSource.delete(isoCode); if (!error) { + this.#languageStore?.remove([isoCode]); const notification = { data: { message: `Language deleted` } }; this.#notificationService?.peek('positive', notification); } - this.#languageStore?.remove([isoCode]); - return { error }; } } From 195fd9817f02c7ebb9c32fd2a1783f63bf46c70b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 21:49:27 +0100 Subject: [PATCH 103/116] add todo --- .../src/backoffice/shared/components/section/section.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts index e5d2999d82..009f40afaf 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts @@ -210,6 +210,7 @@ export class UmbSectionElement extends UmbLitElement { ${this._menuItems && this._menuItems.length > 0 ? html` + From fe49190a75c823d3d613ee642870f0942b6bcf39 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 21:52:28 +0100 Subject: [PATCH 104/116] remove culture warning --- .../edit-language-workspace-view.element.ts | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 159cf58e32..c8a78c340f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -19,29 +19,24 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { display: block; padding: var(--uui-size-space-6); } + uui-combobox { width: 100%; } + hr { border: none; border-bottom: 1px solid var(--uui-color-divider); } - #culture-warning, - #default-language-warning { - padding: var(--uui-size-space-4) var(--uui-size-space-5); - border: 1px solid; - margin-top: var(--uui-size-space-4); - border-radius: var(--uui-border-radius); - } - #culture-warning { - background-color: var(--uui-color-danger); - color: var(--uui-color-danger-contrast); - border-color: var(--uui-color-danger-standalone); - } + #default-language-warning { background-color: var(--uui-color-warning); color: var(--uui-color-warning-contrast); border-color: var(--uui-color-warning-standalone); + padding: var(--uui-size-space-4) var(--uui-size-space-5); + border: 1px solid; + margin-top: var(--uui-size-space-4); + border-radius: var(--uui-border-radius); } `, ]; @@ -116,15 +111,6 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { } } - #renderCultureWarning() { - if (!this._startData?.isoCode || this._startData?.isoCode === this._language?.isoCode) return nothing; - - return html`
    - Changing the culture for a language may be an expensive operation and will result in the content cache and indexes - being rebuilt. -
    `; - } - #renderDefaultLanguageWarning() { if (this._startData?.isDefault || this._language?.isDefault !== true) return nothing; @@ -143,7 +129,6 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { - ${this.#renderCultureWarning()}
    From 1a0d88cfd946c8227eea15e154111dce77941916 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 20 Feb 2023 21:59:59 +0100 Subject: [PATCH 105/116] set the culture to readonly on saved languages --- .../language/language-workspace.context.ts | 2 +- .../edit-language-workspace-view.element.ts | 3 +- .../input-culture-select.element.ts | 63 +++++++++++++------ 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts index 0e21d70a1e..66ca737d14 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts @@ -27,9 +27,9 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { } async createScaffold() { + this.isNew = true; const { data } = await this.#languageRepository.createScaffold(); if (!data) return; - this.isNew = true; this.#data.update(data); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index c8a78c340f..9aee2c7b4a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -128,7 +128,8 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement {
    + @change=${this.#handleCultureChange} + ?readonly=${!this.#languageWorkspaceContext?.isNew}>
    diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts index 050a942a1c..b9b50242e4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-culture-select/input-culture-select.element.ts @@ -1,6 +1,6 @@ import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, state } from 'lit/decorators.js'; +import { customElement, property, state } from 'lit/decorators.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { repeat } from 'lit/directives/repeat.js'; @@ -14,6 +14,24 @@ import { UmbChangeEvent } from 'src/core/events'; export class UmbInputCultureSelectElement extends FormControlMixin(UmbLitElement) { static styles = [UUITextStyles, css``]; + /** + * Disables the input + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + disabled = false; + + /** + * Disables the input + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + readonly = false; + @state() private _cultures: CultureModel[] = []; @@ -50,7 +68,7 @@ export class UmbInputCultureSelectElement extends FormControlMixin(UmbLitElement this.dispatchEvent(new UmbChangeEvent()); } - get #filteredCultures(): Array { + get #filteredCultures() { return this._cultures.filter((culture) => { return culture.englishName?.toLowerCase().includes(this._search.toLowerCase()); }); @@ -61,23 +79,30 @@ export class UmbInputCultureSelectElement extends FormControlMixin(UmbLitElement } render() { - return html` - - ${repeat( - this.#filteredCultures, - (culture) => culture.name, - (culture) => - html` - ${culture.englishName} - ` - )} - - `; + return html` + + ${this.disabled || this.readonly + ? html`${this.#fromAvailableCultures?.englishName}` + : html` + + + ${repeat( + this.#filteredCultures, + (culture) => culture.name, + (culture) => + html` + ${culture.englishName} + ` + )} + + + `} + `; } } From 2d669627eb048604d2737b0b489a26fd528ff4e9 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 21 Feb 2023 09:20:41 +0100 Subject: [PATCH 106/116] fix pr workflow --- .../.github/workflows/pr-first-response.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml b/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml index f3573ade2c..209d401e31 100644 --- a/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml +++ b/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Install dependencies run: | - npm install node-fetch@2 + npm install node-fetch - name: Fetch random comment 🗣️ and add it to the PR uses: actions/github-script@v6 with: From 282b03fd1e98f70cb89123e66818ff0a2aad90b2 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 21 Feb 2023 09:23:18 +0100 Subject: [PATCH 107/116] remove node-fetch install --- .../.github/workflows/pr-first-response.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml b/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml index 209d401e31..fe5225a929 100644 --- a/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml +++ b/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml @@ -11,15 +11,10 @@ jobs: issues: write pull-requests: write steps: - - name: Install dependencies - run: | - npm install node-fetch - name: Fetch random comment 🗣️ and add it to the PR uses: actions/github-script@v6 with: script: | - const fetch = require('node-fetch') - const response = await fetch('https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/comments/PostComment', { method: 'post', body: JSON.stringify({ From 57c2549dbf06a8428c5f1b49bbe1f94956f8d853 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 21 Feb 2023 09:24:48 +0100 Subject: [PATCH 108/116] test workflow --- .../.github/workflows/pr-first-response.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml b/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml index fe5225a929..8bb123ad55 100644 --- a/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml +++ b/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml @@ -1,8 +1,6 @@ name: pr-first-response -on: - pull_request_target: - types: [opened] +on: [pull_request] jobs: send-response: From 09d85f5b00da6389e74a4cc7ae50d0544fbf8b4c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 21 Feb 2023 09:35:15 +0100 Subject: [PATCH 109/116] Revert "test workflow" This reverts commit 57c2549dbf06a8428c5f1b49bbe1f94956f8d853. --- .../.github/workflows/pr-first-response.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml b/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml index 8bb123ad55..fe5225a929 100644 --- a/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml +++ b/src/Umbraco.Web.UI.Client/.github/workflows/pr-first-response.yml @@ -1,6 +1,8 @@ name: pr-first-response -on: [pull_request] +on: + pull_request_target: + types: [opened] jobs: send-response: From 74b2655fc2f710e4f10ffb6d11f161fa4fea8e62 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 21 Feb 2023 10:43:34 +0100 Subject: [PATCH 110/116] prevent having no default language + show message if changing the default language --- .../edit-language-workspace-view.element.ts | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 9aee2c7b4a..5cca4e0ca2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -45,18 +45,28 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { _language?: LanguageModel; @state() - private _startData: LanguageModel | null = null; + _isDefaultLanguage = false; #languageWorkspaceContext?: UmbLanguageWorkspaceContext; constructor() { super(); + let initialStateSet = false; + this.consumeContext('umbWorkspaceContext', (instance) => { this.#languageWorkspaceContext = instance; this.observe(this.#languageWorkspaceContext.data, (language) => { this._language = language; + + /* Store the initial value of the default language. + When we change the default language we get notified of the change, + and we need the initial value to compare against */ + if (initialStateSet === false) { + this._isDefaultLanguage = language?.isDefault ?? false; + initialStateSet = true; + } }); }); } @@ -111,14 +121,6 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { } } - #renderDefaultLanguageWarning() { - if (this._startData?.isDefault || this._language?.isDefault !== true) return nothing; - - return html`
    - Switching default language may result in default content missing. -
    `; - } - render() { if (!this._language) return nothing; @@ -140,7 +142,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement {
    @@ -148,7 +150,14 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement {
    An Umbraco site can only have one default language set.
    - ${this.#renderDefaultLanguageWarning()} + + + ${this._language.isDefault !== this._isDefaultLanguage + ? html`
    + Switching default language may result in default content missing. +
    ` + : nothing} +
    From d3287eca26fddcbd446a8c5b54c2f5ee179c006d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 21 Feb 2023 10:49:34 +0100 Subject: [PATCH 111/116] clean up + add caret --- .../languages/app-language-select.element.ts | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts index b4a981056b..855c2c3d82 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts @@ -29,8 +29,10 @@ export class UmbAppLanguageSelectElement extends UmbLitElement { height: 70px; padding: 0 var(--uui-size-8); border-bottom: 1px solid var(--uui-color-border); - font-size: 1rem; - font-weight: bold; + font-size: 14px; + display: flex; + align-items: center; + justify-content: space-between; } `, ]; @@ -107,22 +109,31 @@ export class UmbAppLanguageSelectElement extends UmbLitElement { render() { return html` - -
    - ${repeat( - this._languages, - (language) => language.isoCode, - (language) => - html` - - ` - )} -
    + ${this.#renderTrigger()} ${this.#renderContent()}
    `; } + + #renderTrigger() { + return html``; + } + + #renderContent() { + return html`
    + ${repeat( + this._languages, + (language) => language.isoCode, + (language) => + html` + + ` + )} +
    `; + } } declare global { From e92c4f17c21fca4160f834b6f6fe5c3a29931c31 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 21 Feb 2023 10:53:10 +0100 Subject: [PATCH 112/116] add hover state --- .../settings/languages/app-language-select.element.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts index 855c2c3d82..5d806919d2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts @@ -33,6 +33,11 @@ export class UmbAppLanguageSelectElement extends UmbLitElement { display: flex; align-items: center; justify-content: space-between; + cursor: pointer; + } + + #toggle:hover { + background-color: var(--uui-color-surface-emphasis); } `, ]; From bd8cda1e80cd2a08cfa61ad7bc7e974ee87ec432 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 21 Feb 2023 10:57:24 +0100 Subject: [PATCH 113/116] set active state on active language --- .../settings/languages/app-language-select.element.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts index 5d806919d2..1415471f37 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/app-language-select.element.ts @@ -134,7 +134,8 @@ export class UmbAppLanguageSelectElement extends UmbLitElement { + data-iso-code=${ifDefined(language.isoCode)} + ?active=${language.isoCode === this._appLanguage?.isoCode}> ` )}
    `; From c7d9b5afb76a56f1e18b952ddf05b52eb64462e6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 21 Feb 2023 11:20:24 +0100 Subject: [PATCH 114/116] make workspace isNew an observable --- .../language/language-workspace.context.ts | 18 ++++++++++++++---- .../edit-language-workspace-view.element.ts | 9 ++++++++- .../workspace-context.interface.ts | 6 +++++- .../workspace-context/workspace-context.ts | 2 -- .../shared/workspace-actions/save.action.ts | 4 ++-- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts index 66ca737d14..860fa6eb69 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts @@ -1,13 +1,15 @@ import { UmbLanguageRepository } from '../../repository/language.repository'; import { UmbWorkspaceContext } from '../../../../shared/components/workspace/workspace-context/workspace-context'; import type { LanguageModel } from '@umbraco-cms/backend-api'; -import { ObjectState } from '@umbraco-cms/observable-api'; +import { DeepState, ObjectState } from '@umbraco-cms/observable-api'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { #host: UmbControllerHostInterface; #languageRepository: UmbLanguageRepository; - isNew = false; + + #isNew = new DeepState(false); + isNew = this.#isNew.asObservable(); #data = new ObjectState(undefined); data = this.#data.asObservable(); @@ -21,15 +23,15 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { async load(isoCode: string) { const { data } = await this.#languageRepository.requestByIsoCode(isoCode); if (data) { - this.isNew = false; + this.#isNew.next(false); this.#data.update(data); } } async createScaffold() { - this.isNew = true; const { data } = await this.#languageRepository.createScaffold(); if (!data) return; + this.#isNew.next(true); this.#data.update(data); } @@ -41,6 +43,14 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { return 'language'; } + getIsNew() { + return this.#isNew.getValue(); + } + + setIsNew(isNew: boolean) { + this.#isNew.next(isNew); + } + setName(name: string) { this.#data.update({ name }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts index 5cca4e0ca2..751237ecd4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/views/edit/edit-language-workspace-view.element.ts @@ -47,6 +47,9 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { @state() _isDefaultLanguage = false; + @state() + _isNew = false; + #languageWorkspaceContext?: UmbLanguageWorkspaceContext; constructor() { @@ -68,6 +71,10 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { initialStateSet = true; } }); + + this.observe(this.#languageWorkspaceContext.isNew, (value) => { + this._isNew = value; + }); }); } @@ -131,7 +138,7 @@ export class UmbEditLanguageWorkspaceViewElement extends UmbLitElement { + ?readonly=${this._isNew === false}>
    diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts index 219aab4e2b..d3bb53e38b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts @@ -1,7 +1,11 @@ +import { Observable } from 'rxjs'; + export interface UmbWorkspaceContextInterface { //readonly data: Observable; //getUnique(): string | undefined; - isNew: boolean; + isNew: Observable; + getIsNew(): boolean; + setIsNew(value: boolean): void; getEntityType(): string; getData(): T; destroy(): void; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts index 1ee9462a36..d0df62f4e2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts @@ -7,12 +7,10 @@ TODO: We need to figure out if we like to keep using same alias for all workspac If so we need to align on a interface that all of these implements. otherwise consumers cant trust the workspace-context. */ export abstract class UmbWorkspaceContext { - protected _host: UmbControllerHostInterface; constructor(host: UmbControllerHostInterface) { this._host = host; new UmbContextProviderController(host, 'umbWorkspaceContext', this); } - } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts index b42c834fe1..7c51ab0d96 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/workspace-actions/save.action.ts @@ -15,9 +15,9 @@ export class UmbSaveWorkspaceAction extends UmbWorkspaceAction Date: Tue, 21 Feb 2023 12:40:06 +0100 Subject: [PATCH 115/116] update workspace contexts --- .../document-type-workspace.context.ts | 1 - .../workspace/document-workspace.context.ts | 7 +++---- .../workspace/media-type-workspace.context.ts | 5 +++-- .../media/workspace/media-workspace.context.ts | 7 +++---- .../workspace/member-group-workspace.context.ts | 5 +++-- .../workspace/member-type-workspace.context.ts | 7 +++---- .../workspace/member-workspace.context.ts | 1 - .../workspace/data-type-workspace.context.ts | 7 +++---- .../language/language-workspace.context.ts | 17 +++-------------- .../workspace-context.interface.ts | 4 +--- .../workspace-context/workspace-context.ts | 12 ++++++++++++ .../workspace-entity-context.interface.ts | 5 ----- .../workspace/dictionary-workspace.context.ts | 6 ++++-- .../workspace/user-group-workspace.context.ts | 2 -- .../users/workspace/user-workspace.context.ts | 1 - 15 files changed, 38 insertions(+), 49 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts index efe6f99b1d..da36b169cb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts @@ -8,7 +8,6 @@ export class UmbWorkspaceDocumentTypeContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; #manager = new UmbEntityWorkspaceManager(this._host, 'document-type', UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN); public readonly data = this.#manager.state.asObservable(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts index 1ace3a7c48..745deb59d7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts @@ -19,7 +19,6 @@ export class UmbDocumentWorkspaceContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; #host: UmbControllerHostInterface; #documentRepository: UmbDocumentRepository; #documentTypeRepository: UmbDocumentTypeRepository; @@ -44,7 +43,7 @@ export class UmbDocumentWorkspaceContext async load(entityKey: string) { const { data } = await this.#documentRepository.requestByKey(entityKey); if (data) { - this.isNew = false; + this.setIsNew(false); this.#data.next(data); } } @@ -52,7 +51,7 @@ export class UmbDocumentWorkspaceContext async createScaffold(parentKey: string | null) { const { data } = await this.#documentRepository.createScaffold(parentKey); if (!data) return; - this.isNew = true; + this.setIsNew(true); this.#data.next(data); } @@ -180,7 +179,7 @@ export class UmbDocumentWorkspaceContext await this.#documentRepository.save(this.#data.value); } // If it went well, then its not new anymore?. - this.isNew = false; + this.setIsNew(false); } async delete(key: string) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts index 871b1ef489..2445086e43 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/workspace/media-type-workspace.context.ts @@ -10,7 +10,6 @@ export class UmbWorkspaceMediaTypeContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; #host: UmbControllerHostInterface; #repo: UmbMediaTypeRepository; @@ -54,12 +53,14 @@ export class UmbWorkspaceMediaTypeContext async createScaffold() { const { data } = await this.#repo.createScaffold(); if (!data) return; + this.setIsNew(true); this.#data.next(data); } async save() { if (!this.#data.value) return; - this.#repo.save(this.#data.value); + await this.#repo.save(this.#data.value); + this.setIsNew(false); } public destroy(): void { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts index 4905458014..fe27ecb936 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts @@ -10,7 +10,6 @@ export class UmbMediaWorkspaceContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; #host: UmbControllerHostInterface; #detailRepository: UmbMediaRepository; @@ -54,7 +53,7 @@ export class UmbMediaWorkspaceContext async load(entityKey: string) { const { data } = await this.#detailRepository.requestByKey(entityKey); if (data) { - this.isNew = false; + this.setIsNew(false); this.#data.next(data); } } @@ -62,7 +61,7 @@ export class UmbMediaWorkspaceContext async createScaffold(parentKey: string | null) { const { data } = await this.#detailRepository.createScaffold(parentKey); if (!data) return; - this.isNew = true; + this.setIsNew(true); this.#data.next(data); } @@ -74,7 +73,7 @@ export class UmbMediaWorkspaceContext await this.#detailRepository.save(this.#data.value); } // If it went well, then its not new anymore?. - this.isNew = false; + this.setIsNew(false); } async delete(key: string) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts index 335d2d8524..194e50c0ae 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts @@ -10,7 +10,6 @@ export class UmbWorkspaceMemberGroupContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; #host: UmbControllerHostInterface; #repo: UmbMemberGroupRepository; @@ -55,12 +54,14 @@ export class UmbWorkspaceMemberGroupContext async createScaffold() { const { data } = await this.#repo.createScaffold(); if (!data) return; + this.setIsNew(true); this.#data.next(data); } async save() { if (!this.#data.value) return; - this.#repo.save(this.#data.value); + await this.#repo.save(this.#data.value); + this.setIsNew(true); } public destroy(): void { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts index 00e0ef78ea..166d07c469 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts @@ -11,7 +11,6 @@ export class UmbWorkspaceMemberTypeContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; #host: UmbControllerHostInterface; #dataTypeRepository: UmbMemberTypeRepository; @@ -27,7 +26,7 @@ export class UmbWorkspaceMemberTypeContext async load(entityKey: string) { const { data } = await this.#dataTypeRepository.requestByKey(entityKey); if (data) { - this.isNew = false; + this.setIsNew(false); this.#data.next(data); } } @@ -35,7 +34,7 @@ export class UmbWorkspaceMemberTypeContext async createScaffold() { const { data } = await this.#dataTypeRepository.createScaffold(); if (!data) return; - this.isNew = true; + this.setIsNew(true); this.#data.next(data); } @@ -67,7 +66,7 @@ export class UmbWorkspaceMemberTypeContext await this.#dataTypeRepository.save(this.#data.value); } // If it went well, then its not new anymore?. - this.isNew = false; + this.setIsNew(false); } async delete(key: string) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts index 0643082bba..e3ea0a4a1a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/workspace/member-workspace.context.ts @@ -8,7 +8,6 @@ export class UmbWorkspaceMemberContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; #manager = new UmbEntityWorkspaceManager(this._host, 'member', UMB_MEMBER_DETAIL_STORE_CONTEXT_TOKEN); public readonly data = this.#manager.state.asObservable(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts index e8b10c2c24..e07767d67a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts @@ -9,7 +9,6 @@ export class UmbDataTypeWorkspaceContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; #host: UmbControllerHostInterface; #dataTypeRepository: UmbDataTypeRepository; @@ -27,15 +26,15 @@ export class UmbDataTypeWorkspaceContext async load(key: string) { const { data } = await this.#dataTypeRepository.requestByKey(key); if (data) { - this.isNew = false; + this.setIsNew(false); this.#data.update(data); } } async createScaffold(parentKey: string | null) { - this.isNew = true; const { data } = await this.#dataTypeRepository.createScaffold(parentKey); if (!data) return; + this.setIsNew(true); this.#data.next(data); } @@ -82,7 +81,7 @@ export class UmbDataTypeWorkspaceContext await this.#dataTypeRepository.save(this.#data.value); } // If it went well, then its not new anymore?. - this.isNew = false; + this.setIsNew(false); } async delete(key: string) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts index 860fa6eb69..8744f0c8f9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/workspace/language/language-workspace.context.ts @@ -1,16 +1,13 @@ import { UmbLanguageRepository } from '../../repository/language.repository'; import { UmbWorkspaceContext } from '../../../../shared/components/workspace/workspace-context/workspace-context'; import type { LanguageModel } from '@umbraco-cms/backend-api'; -import { DeepState, ObjectState } from '@umbraco-cms/observable-api'; +import { ObjectState } from '@umbraco-cms/observable-api'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { #host: UmbControllerHostInterface; #languageRepository: UmbLanguageRepository; - #isNew = new DeepState(false); - isNew = this.#isNew.asObservable(); - #data = new ObjectState(undefined); data = this.#data.asObservable(); @@ -23,7 +20,7 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { async load(isoCode: string) { const { data } = await this.#languageRepository.requestByIsoCode(isoCode); if (data) { - this.#isNew.next(false); + this.setIsNew(false); this.#data.update(data); } } @@ -31,7 +28,7 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { async createScaffold() { const { data } = await this.#languageRepository.createScaffold(); if (!data) return; - this.#isNew.next(true); + this.setIsNew(true); this.#data.update(data); } @@ -43,14 +40,6 @@ export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext { return 'language'; } - getIsNew() { - return this.#isNew.getValue(); - } - - setIsNew(isNew: boolean) { - this.#isNew.next(isNew); - } - setName(name: string) { this.#data.update({ name }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts index d3bb53e38b..6b6a0332a9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts @@ -1,9 +1,7 @@ import { Observable } from 'rxjs'; export interface UmbWorkspaceContextInterface { - //readonly data: Observable; - //getUnique(): string | undefined; - isNew: Observable; + isNew: Observable; getIsNew(): boolean; setIsNew(value: boolean): void; getEntityType(): string; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts index d0df62f4e2..ca0407059d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts @@ -1,5 +1,6 @@ import { UmbContextProviderController } from '@umbraco-cms/context-api'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { DeepState } from '@umbraco-cms/observable-api'; /* @@ -9,8 +10,19 @@ If so we need to align on a interface that all of these implements. otherwise co export abstract class UmbWorkspaceContext { protected _host: UmbControllerHostInterface; + #isNew = new DeepState(false); + isNew = this.#isNew.asObservable(); + constructor(host: UmbControllerHostInterface) { this._host = host; new UmbContextProviderController(host, 'umbWorkspaceContext', this); } + + getIsNew() { + return this.#isNew.getValue(); + } + + setIsNew(isNew: boolean) { + this.#isNew.next(isNew); + } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-entity-context.interface.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-entity-context.interface.ts index 87fd3e526e..6d15dead5d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-entity-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-entity-context.interface.ts @@ -1,14 +1,9 @@ import { UmbWorkspaceContextInterface } from './workspace-context.interface'; export interface UmbWorkspaceEntityContextInterface extends UmbWorkspaceContextInterface { - //readonly name: Observable; - getEntityKey(): string | undefined; // COnsider if this should go away now that we have getUnique() getEntityType(): string; - getData(): T; - setPropertyValue(alias: string, value: unknown): void; - save(): Promise; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts index c926b618dc..d8d1080ead 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts @@ -10,7 +10,6 @@ export class UmbWorkspaceDictionaryContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; #host: UmbControllerHostInterface; #repo: UmbDictionaryRepository; @@ -64,6 +63,7 @@ export class UmbWorkspaceDictionaryContext async load(entityKey: string) { const { data } = await this.#repo.requestByKey(entityKey); if (data) { + this.setIsNew(false); this.#data.next(data); } } @@ -71,12 +71,14 @@ export class UmbWorkspaceDictionaryContext async createScaffold(parentKey: string | null) { const { data } = await this.#repo.createScaffold(parentKey); if (!data) return; + this.setIsNew(true); this.#data.next(data); } async save() { if (!this.#data.value) return; - this.#repo.save(this.#data.value); + await this.#repo.save(this.#data.value); + this.setIsNew(false); } public destroy(): void { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts index e7d36244a3..8f7d07ef24 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts @@ -8,8 +8,6 @@ export class UmbWorkspaceUserGroupContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; - #manager = new UmbEntityWorkspaceManager(this._host, 'user-group', UMB_USER_GROUP_STORE_CONTEXT_TOKEN); public readonly data = this.#manager.state.asObservable(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts index 7f1dd39cb5..5552864df8 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts @@ -8,7 +8,6 @@ export class UmbWorkspaceUserContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { - isNew = false; #manager = new UmbEntityWorkspaceManager(this._host, 'user', UMB_USER_STORE_CONTEXT_TOKEN); public readonly data = this.#manager.state.asObservable(); From 9f4ae16c14d4a52495fed22419bc80005b3bd0f6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 21 Feb 2023 13:15:04 +0100 Subject: [PATCH 116/116] fix typescript errors --- .../sources/document-type.server.data.ts | 22 ++++++++++++++----- .../sources/document.server.data.ts | 21 ++++++++++++------ .../sources/media.detail.server.data.ts | 20 ++++++++++++----- .../input-language-picker.element.ts | 2 +- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/sources/document-type.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/sources/document-type.server.data.ts index 3bb0855410..f014c438ba 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/sources/document-type.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/sources/document-type.server.data.ts @@ -157,23 +157,33 @@ export class UmbDocumentTypeServerDataSource implements RepositoryDetailDataSour * @return {*} * @memberof UmbDocumentTypeServerDataSource */ - // TODO: Error mistake in this: async delete(key: string) { if (!key) { const error: ProblemDetailsModel = { title: 'Key is missing' }; return { error }; } - // TODO: use resources when end point is ready: - return tryExecuteAndNotify( - this.#host, - fetch('/umbraco/management/api/v1/document-type/trash', { + let problemDetails: ProblemDetailsModel | undefined = undefined; + + try { + await fetch('/umbraco/management/api/v1/document-type/trash', { method: 'POST', body: JSON.stringify([key]), headers: { 'Content-Type': 'application/json', }, - }) + }); + } catch (error) { + problemDetails = { title: 'Delete document Failed' }; + } + + return { error: problemDetails }; + + // TODO: use resources when end point is ready: + /* + return tryExecuteAndNotify( + this.#host, ); + */ } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/document.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/document.server.data.ts index 41480a0d0f..3807b77c1d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/document.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/document.server.data.ts @@ -158,23 +158,30 @@ export class UmbDocumentServerDataSource implements RepositoryDetailDataSource - + this._removeItem(item)} label="Remove ${item.name}">Remove