This commit is contained in:
Lone Iversen
2023-02-02 13:12:52 +01:00
parent 387bc3cf1a
commit a3912d1ace
5 changed files with 245 additions and 10 deletions

View File

@@ -14,3 +14,5 @@ import './section/section-sidebar/section-sidebar.element';
import './section/section.element';
import './tree/tree.element';
import './workspace/workspace-content/workspace-content.element';
import './input-media-picker/input-media-picker.element';
import './input-document-picker/input-document-picker.element';

View File

@@ -21,6 +21,13 @@ export class UmbInputDocumentPickerElement extends FormControlMixin(UmbLitElemen
`,
];
/**
* Set picker to singlemode
* @attr
*/
@property({ type: Boolean })
singlemode?: boolean;
/**
* This is a minimum amount of selected items in this input.
* @type {number}
@@ -122,7 +129,10 @@ export class UmbInputDocumentPickerElement extends FormControlMixin(UmbLitElemen
private _openPicker() {
// We send a shallow copy(good enough as its just an array of keys) of our this._selectedKeys, as we don't want the modal to manipulate our data:
const modalHandler = this._modalService?.contentPicker({ multiple: true, selection: [...this._selectedKeys] });
const modalHandler = this._modalService?.contentPicker({
multiple: this.singlemode ? false : true,
selection: [...this._selectedKeys],
});
modalHandler?.onClose().then(({ selection }: any) => {
this._setSelection(selection);
});

View File

@@ -0,0 +1,194 @@
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 {
MediaTreeItem,
UmbMediaTreeStore,
UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN,
} from '../../../../backoffice/media/media/media.tree.store';
import { UmbLitElement } from '@umbraco-cms/element';
import type { FolderTreeItem } from '@umbraco-cms/backend-api';
import type { UmbObserverController } from '@umbraco-cms/observable-api';
@customElement('umb-input-media-picker')
export class UmbInputMediaPickerElement extends FormControlMixin(UmbLitElement) {
static styles = [
UUITextStyles,
css`
#add-button {
width: 100%;
}
`,
];
/**
* Set picker to singlemode
* @attr
*/
@property({ type: Boolean })
singlemode?: boolean;
/**
* 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';
// 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<string> = [];
public get selectedKeys(): Array<string> {
return this._selectedKeys;
}
public set selectedKeys(keys: Array<string>) {
this._selectedKeys = keys;
super.value = keys.join(',');
this._observePickedMedias();
}
@property()
public set value(keysString: string) {
if (keysString !== this._value) {
this.selectedKeys = keysString.split(/[ ,]+/);
}
}
@state()
private _items?: Array<MediaTreeItem>;
private _modalService?: UmbModalService;
private _mediaStore?: UmbMediaTreeStore;
private _pickedItemsObserver?: UmbObserverController<FolderTreeItem>;
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_MEDIA_TREE_STORE_CONTEXT_TOKEN, (instance) => {
this._mediaStore = instance;
this._observePickedMedias();
});
this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => {
this._modalService = instance;
});
}
protected getFormElement() {
return undefined;
}
private _observePickedMedias() {
this._pickedItemsObserver?.destroy();
if (!this._mediaStore) return;
// TODO: consider changing this to the list data endpoint when it is available
this._pickedItemsObserver = this.observe(this._mediaStore.getTreeItems(this._selectedKeys), (items) => {
this._items = items;
});
}
private _openPicker() {
// We send a shallow copy(good enough as its just an array of keys) of our this._selectedKeys, as we don't want the modal to manipulate our data:
const modalHandler = this._modalService?.mediaPicker({
multiple: this.singlemode ? false : true,
selection: [...this._selectedKeys],
});
modalHandler?.onClose().then(({ selection }: any) => {
this._setSelection(selection);
});
}
private _removeItem(item: FolderTreeItem) {
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._selectedKeys.filter((value) => value !== item.key);
this._setSelection(newSelection);
}
});
}
private _setSelection(newSelection: Array<string>) {
this.selectedKeys = newSelection;
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
}
render() {
return html`
${this._items?.map((item) => this._renderItem(item))}
<uui-button id="add-button" look="placeholder" @click=${this._openPicker} label="open">Add</uui-button>
`;
}
private _renderItem(item: FolderTreeItem) {
// TODO: remove when we have a way to handle trashed items
const tempItem = item as FolderTreeItem & { isTrashed: boolean };
return html`
<uui-ref-node name=${ifDefined(item.name === null ? undefined : item.name)} detail=${ifDefined(item.key)}>
${tempItem.isTrashed ? html` <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> ` : nothing}
<uui-action-bar slot="actions">
<uui-button @click=${() => this._removeItem(item)} label="Remove media ${item.name}">Remove</uui-button>
</uui-action-bar>
<uui-icon slot="icon" name=${ifDefined('umb:' + item.icon)}></uui-icon>
</uui-ref-node>
`;
}
}
export default UmbInputMediaPickerElement;
declare global {
interface HTMLElementTagNameMap {
'umb-input-media-picker': UmbInputMediaPickerElement;
}
}

View File

@@ -1,8 +1,7 @@
import { html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { UmbInputDocumentPickerElement } from '../../../components/input-document-picker/input-document-picker.element';
import { UmbLitElement } from '@umbraco-cms/element';
import type { UmbInputDocumentPickerElement } from 'src/backoffice/shared/components/input-document-picker/input-document-picker.element';
import '../../../components/input-document-picker/input-document-picker.element';
import type { DataTypePropertyData } from '@umbraco-cms/models';
@customElement('umb-property-editor-ui-document-picker')

View File

@@ -1,23 +1,53 @@
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property, state } from 'lit/decorators.js';
import { UmbInputMediaPickerElement } from '../../../../../backoffice/shared/components/input-media-picker/input-media-picker.element';
import { UmbLitElement } from '@umbraco-cms/element';
import type { DataTypePropertyData } from '@umbraco-cms/models';
/**
* @element umb-property-editor-ui-media-picker
*/
@customElement('umb-property-editor-ui-media-picker')
export class UmbPropertyEditorUIMediaPickerElement extends UmbLitElement {
static styles = [UUITextStyles];
private _value: Array<string> = [];
@property()
value = '';
@property({ type: Array })
public get value(): Array<string> {
return this._value;
}
public set value(value: Array<string>) {
this._value = value || [];
}
@property({ type: Array, attribute: false })
public config = [];
public set config(config: Array<DataTypePropertyData>) {
const validationLimit = config.find((x) => x.alias === 'validationLimit');
if (!validationLimit) return;
this._limitMin = (validationLimit?.value as any).min;
this._limitMax = (validationLimit?.value as any).max;
}
@state()
private _limitMin?: number;
@state()
private _limitMax?: number;
private _onChange(event: CustomEvent) {
this.value = (event.target as UmbInputMediaPickerElement).selectedKeys;
this.dispatchEvent(new CustomEvent('property-value-change'));
}
render() {
return html`<div>umb-property-editor-ui-media-picker</div>`;
return html`
<umb-input-media-picker
@change=${this._onChange}
.selectedKeys=${this._value}
.min=${this._limitMin}
.max=${this._limitMax}
>Add</umb-input-media-picker
>
`;
}
}