From b85e11f8a1b40e04642ab533fbd2511cd773333f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Sep 2023 10:46:56 +0200 Subject: [PATCH] update media input to use picker context --- .../input-media/input-media.context.ts | 10 ++ .../input-media/input-media.element.ts | 137 ++++++------------ 2 files changed, 53 insertions(+), 94 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.context.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.context.ts new file mode 100644 index 0000000000..cb668fd2f2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.context.ts @@ -0,0 +1,10 @@ +import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UMB_MEDIA_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; +import { MediaItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +export class UmbMediaPickerContext extends UmbPickerInputContext { + constructor(host: UmbControllerHostElement) { + super(host, 'Umb.Repository.Media', UMB_MEDIA_TREE_PICKER_MODAL); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts index a6cc059d08..21749f87f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts @@ -1,15 +1,8 @@ -import { UmbMediaRepository } from '../../repository/media.repository.js'; -import { css, html, nothing, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { UmbMediaPickerContext } from './input-media.context.js'; +import { css, html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; -import { - UmbModalManagerContext, - UMB_MODAL_MANAGER_CONTEXT_TOKEN, - UMB_CONFIRM_MODAL, - UMB_MEDIA_TREE_PICKER_MODAL, -} from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import type { MediaItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @customElement('umb-input-media') export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { @@ -17,10 +10,15 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { * This is a minimum amount of selected items in this input. * @type {number} * @attr - * @default undefined + * @default 0 */ @property({ type: Number }) - min?: number; + public get min(): number { + return this.#pickerContext.min; + } + public set min(value: number) { + this.#pickerContext.min = value; + } /** * Min validation message. @@ -35,10 +33,15 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { * This is a maximum amount of selected items in this input. * @type {number} * @attr - * @default undefined + * @default Infinity */ @property({ type: Number }) - max?: number; + public get max(): number { + return this.#pickerContext.max; + } + public set max(value: number) { + this.#pickerContext.max = value; + } /** * Max validation message. @@ -49,30 +52,23 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { @property({ type: String, attribute: 'min-message' }) maxMessage = 'This field exceeds the allowed amount of items'; - // TODO: do we need both selectedIds and value? If we just use value we follow the same pattern as native form controls. - private _selectedIds: Array = []; public get selectedIds(): Array { - return this._selectedIds; + return this.#pickerContext.getSelection(); } public set selectedIds(ids: Array) { - this._selectedIds = ids; - super.value = ids.join(','); - this._observePickedMedias(); + this.#pickerContext.setSelection(ids); } @property() public set value(idsString: string) { - if (idsString !== this._value) { - this.selectedIds = idsString.split(/[ ,]+/); - } + // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. + this.selectedIds = idsString.split(/[ ,]+/); } @state() - private _items?: Array; + private _items?: Array; - private _modalContext?: UmbModalManagerContext; - private _pickedItemsObserver?: UmbObserverController; - private _repository = new UmbMediaRepository(this); + #pickerContext = new UmbMediaPickerContext(this); constructor() { super(); @@ -80,104 +76,56 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { this.addValidator( 'rangeUnderflow', () => this.minMessage, - () => !!this.min && this._selectedIds.length < this.min + () => !!this.min && this.#pickerContext.getSelection().length < this.min, ); + this.addValidator( 'rangeOverflow', () => this.maxMessage, - () => !!this.max && this._selectedIds.length > this.max + () => !!this.max && this.#pickerContext.getSelection().length > this.max, ); - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => { - this._modalContext = instance; - }); - } - - connectedCallback(): void { - super.connectedCallback(); - this._observePickedMedias(); + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); } protected getFormElement() { return undefined; } - private async _observePickedMedias() { - this._pickedItemsObserver?.destroy(); - - // TODO: consider changing this to the list data endpoint when it is available - const { asObservable } = await this._repository.requestItemsLegacy(this._selectedIds); - - if (!asObservable) return; - - this._pickedItemsObserver = this.observe(asObservable(), (items) => { - this._items = items; - }); - } - - private _openPicker() { - // We send a shallow copy(good enough as its just an array of ids) of our this._selectedIds, as we don't want the modal to manipulate our data: - const modalContext = this._modalContext?.open(UMB_MEDIA_TREE_PICKER_MODAL, { - multiple: this.max === 1 ? false : true, - selection: [...this._selectedIds], - }); - - modalContext?.onSubmit().then(({ selection }: any) => { - this._setSelection(selection); - }); - } - - private _removeItem(item: EntityTreeItemResponseModel) { - const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, { - color: 'danger', - headline: `Remove ${item.name}?`, - content: 'Are you sure you want to remove this item', - confirmLabel: 'Remove', - }); - - modalContext?.onSubmit().then(() => { - const newSelection = this._selectedIds.filter((value) => value !== item.id); - this._setSelection(newSelection); - }); - } - - private _setSelection(newSelection: Array) { - this.selectedIds = newSelection; - this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); - } - render() { - return html` ${this._items?.map((item) => this._renderItem(item))} ${this._renderButton()} `; + return html` + ${this._items?.map((item) => this.#renderItem(item))} ${this.#renderButton()} + `; } - private _renderButton() { + + #renderButton() { if (this._items && this.max && this._items.length >= this.max) return; - return html` - - Add - `; + return html` + this.#pickerContext.openPicker()} label="open"> + + Add + + `; } - private _renderItem(item: EntityTreeItemResponseModel) { - // TODO: remove when we have a way to handle trashed items - const tempItem = item as EntityTreeItemResponseModel & { isTrashed: boolean }; - + #renderItem(item: MediaItemResponseModel) { return html` - ${tempItem.isTrashed ? html` Trashed ` : nothing} + - this._removeItem(item)} label="Remove media ${item.name}"> + this.#pickerContext.requestRemoveItem(item.id!)} label="Remove media ${item.name}"> `; - //TODO: } static styles = [ @@ -187,6 +135,7 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { gap: var(--uui-size-space-3); grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); } + #add-button { text-align: center; height: 160px;