update media input to use picker context
This commit is contained in:
@@ -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<MediaItemResponseModel> {
|
||||
constructor(host: UmbControllerHostElement) {
|
||||
super(host, 'Umb.Repository.Media', UMB_MEDIA_TREE_PICKER_MODAL);
|
||||
}
|
||||
}
|
||||
@@ -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<string> = [];
|
||||
public get selectedIds(): Array<string> {
|
||||
return this._selectedIds;
|
||||
return this.#pickerContext.getSelection();
|
||||
}
|
||||
public set selectedIds(ids: Array<string>) {
|
||||
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<EntityTreeItemResponseModel>;
|
||||
private _items?: Array<MediaItemResponseModel>;
|
||||
|
||||
private _modalContext?: UmbModalManagerContext;
|
||||
private _pickedItemsObserver?: UmbObserverController<EntityTreeItemResponseModel[]>;
|
||||
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<string>) {
|
||||
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`
|
||||
<uui-ref-list>${this._items?.map((item) => this.#renderItem(item))}</uui-ref-list> ${this.#renderButton()}
|
||||
`;
|
||||
}
|
||||
private _renderButton() {
|
||||
|
||||
#renderButton() {
|
||||
if (this._items && this.max && this._items.length >= this.max) return;
|
||||
return html`<uui-button id="add-button" look="placeholder" @click=${this._openPicker} label="open">
|
||||
<uui-icon name="umb:add"></uui-icon>
|
||||
Add
|
||||
</uui-button>`;
|
||||
return html`
|
||||
<uui-button id="add-button" look="placeholder" @click=${() => this.#pickerContext.openPicker()} label="open">
|
||||
<uui-icon name="umb:add"></uui-icon>
|
||||
Add
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
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`
|
||||
<uui-card-media
|
||||
name=${ifDefined(item.name === null ? undefined : item.name)}
|
||||
detail=${ifDefined(item.id)}
|
||||
file-ext="jpg">
|
||||
${tempItem.isTrashed ? html` <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> ` : nothing}
|
||||
<!-- <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> -->
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button label="Copy media">
|
||||
<uui-icon name="umb:documents"></uui-icon>
|
||||
</uui-button>
|
||||
<uui-button @click=${() => this._removeItem(item)} label="Remove media ${item.name}">
|
||||
<uui-button @click=${() => this.#pickerContext.requestRemoveItem(item.id!)} label="Remove media ${item.name}">
|
||||
<uui-icon name="umb:trash"></uui-icon>
|
||||
</uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-card-media>
|
||||
`;
|
||||
//TODO: <uui-button-inline-create vertical></uui-button-inline-create>
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user