modal picker for parentid

This commit is contained in:
Lone Iversen
2023-11-15 15:44:00 +01:00
parent 45a2c0a01d
commit d31c7169a7
8 changed files with 225 additions and 66 deletions

View File

@@ -5,9 +5,8 @@ export interface UmbImportDictionaryModalData {
}
export interface UmbImportDictionaryModalValue {
temporaryFileId?: string;
temporaryFileId: string;
parentId?: string;
blob?: Blob;
}
export const UMB_IMPORT_DICTIONARY_MODAL = new UmbModalToken<

View File

@@ -0,0 +1,10 @@
import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UMB_DICTIONARY_ITEM_PICKER_MODAL } from '@umbraco-cms/backoffice/modal';
import { DictionaryItemItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
export class UmbDictionaryItemPickerContext extends UmbPickerInputContext<DictionaryItemItemResponseModel> {
constructor(host: UmbControllerHostElement) {
super(host, 'Umb.Repository.Dictionary', UMB_DICTIONARY_ITEM_PICKER_MODAL);
}
}

View File

@@ -0,0 +1,149 @@
import { UmbDictionaryItemPickerContext } from './dictionary-item-input.context.js';
import { css, html, customElement, property, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit';
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { DictionaryItemItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
@customElement('umb-dictionary-item-input')
export class UmbDictionaryItemInputElement extends FormControlMixin(UmbLitElement) {
/**
* This is a minimum amount of selected items in this input.
* @type {number}
* @attr
* @default 0
*/
@property({ type: Number })
public get min(): number {
return this.#pickerContext.min;
}
public set min(value: number) {
this.#pickerContext.min = value;
}
/**
* 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 Infinity
*/
@property({ type: Number })
public get max(): number {
return this.#pickerContext.max;
}
public set max(value: number) {
this.#pickerContext.max = value;
}
/**
* Max validation message.
* @type {boolean}
* @attr
* @default
*/
@property({ type: String, attribute: 'min-message' })
maxMessage = 'This field exceeds the allowed amount of items';
public get selectedIds(): Array<string> {
return this.#pickerContext.getSelection();
}
public set selectedIds(ids: Array<string>) {
this.#pickerContext.setSelection(ids);
}
@property()
public set value(idsString: string) {
// 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<DictionaryItemItemResponseModel>;
#pickerContext = new UmbDictionaryItemPickerContext(this);
constructor() {
super();
this.addValidator(
'rangeUnderflow',
() => this.minMessage,
() => !!this.min && this.#pickerContext.getSelection().length < this.min,
);
this.addValidator(
'rangeOverflow',
() => this.maxMessage,
() => !!this.max && this.#pickerContext.getSelection().length > this.max,
);
this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(',')));
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
}
protected getFormElement() {
return undefined;
}
render() {
return html`
${this._items
? html` <uui-ref-list
>${repeat(
this._items,
(item) => item.id,
(item) => this._renderItem(item),
)}
</uui-ref-list>`
: ''}
${this.#renderAddButton()}
`;
}
#renderAddButton() {
if (this.max > 0 && this.selectedIds.length >= this.max) return;
return html`<uui-button
id="add-button"
look="placeholder"
@click=${() => this.#pickerContext.openPicker()}
label=${this.localize.term('general_add')}></uui-button>`;
}
private _renderItem(item: DictionaryItemItemResponseModel) {
if (!item.id) return;
return html`
<uui-ref-node name=${ifDefined(item.name)} detail=${ifDefined(item.id)}>
<!-- TODO: implement is trashed <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> -->
<uui-action-bar slot="actions">
<uui-button
@click=${() => this.#pickerContext.requestRemoveItem(item.id!)}
label=${this.localize.term('actions_remove')}></uui-button>
</uui-action-bar>
</uui-ref-node>
`;
}
static styles = [
css`
#add-button {
width: 100%;
}
`,
];
}
export default UmbDictionaryItemInputElement;
declare global {
interface HTMLElementTagNameMap {
'umb-dictionary-item-input': UmbDictionaryItemInputElement;
}
}

View File

@@ -0,0 +1 @@
export * from './dictionary-item-input/dictionary-item-input.element.js';

View File

@@ -1,5 +1,6 @@
import '../../components/dictionary-item-input/dictionary-item-input.element.js';
import UmbDictionaryItemInputElement from '../../components/dictionary-item-input/dictionary-item-input.element.js';
import { UmbDictionaryRepository } from '../../repository/dictionary.repository.js';
import { UUIInputFileElement } from '@umbraco-cms/backoffice/external/uui';
import { css, html, customElement, query, state, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import {
@@ -10,9 +11,9 @@ import {
import { ImportDictionaryRequestModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbId } from '@umbraco-cms/backoffice/id';
interface DictionaryItem {
interface DictionaryItemPreview {
name: string;
children: Array<DictionaryItem>;
children: Array<DictionaryItemPreview>;
}
@customElement('umb-import-dictionary-modal')
@@ -29,42 +30,18 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
@query('#form')
private _form!: HTMLFormElement;
@query('#file')
private _fileInput!: UUIInputFileElement;
#fileReader;
#fileContent: Array<DictionaryItem> = [];
#fileContent: Array<DictionaryItemPreview> = [];
#handleClose() {
this.modalContext?.reject();
}
#submit() {
this._form.requestSubmit();
}
#handleSubmit(e: Event) {
e.preventDefault();
const formData = new FormData(this._form);
const file = formData.get('file') as File;
this._temporaryFileId = file ? UmbId.new() : undefined;
this.#fileReader.readAsText(file);
// TODO: Gotta do a temp file upload before submitting, so that the server can use it
console.log('submit:', this._temporaryFileId, this._parentId);
//this.modalContext?.submit({ temporaryFileId: this._temporaryFileId, parentId: this._parentId });
//this.modalContext?.submit();
}
async #onFileInput() {
requestAnimationFrame(() => {
this._form.requestSubmit();
});
}
#onClear() {
this._temporaryFileId = '';
}
constructor() {
@@ -78,6 +55,11 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
};
}
connectedCallback(): void {
super.connectedCallback();
this._parentId = this.data?.unique ?? undefined;
}
#dictionaryItemBuilder(htmlString: string) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
@@ -87,8 +69,8 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
this.requestUpdate();
}
#makeDictionaryItems(nodeList: NodeListOf<ChildNode>): Array<DictionaryItem> {
const items: Array<DictionaryItem> = [];
#makeDictionaryItems(nodeList: NodeListOf<ChildNode>): Array<DictionaryItemPreview> {
const items: Array<DictionaryItemPreview> = [];
const list: Array<Element> = [];
nodeList.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
@@ -105,9 +87,28 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
return items;
}
connectedCallback(): void {
super.connectedCallback();
this._parentId = this.data?.unique ?? undefined;
#onUpload(e: Event) {
e.preventDefault();
const formData = new FormData(this._form);
const file = formData.get('file') as Blob;
this.#fileReader.readAsText(file);
this._temporaryFileId = file ? UmbId.new() : undefined;
}
#onParentChange(event: CustomEvent) {
this._parentId = (event.target as UmbDictionaryItemInputElement).selectedIds[0] || undefined;
//console.log((event.target as UmbDictionaryItemInputElement).selectedIds[0] || undefined);
}
async #onFileInput() {
requestAnimationFrame(() => {
this._form.requestSubmit();
});
}
#onClear() {
this._temporaryFileId = '';
}
render() {
@@ -127,8 +128,8 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
</umb-body-layout>`;
}
#renderFileContents(items: Array<DictionaryItem>): any {
return html`${items.map((item: DictionaryItem) => {
#renderFileContents(items: Array<DictionaryItemPreview>): any {
return html`${items.map((item: DictionaryItemPreview) => {
return html`${item.name}
<div>${this.#renderFileContents(item.children)}</div>`;
})}`;
@@ -136,23 +137,26 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
#renderImportDestination() {
return html`
<div>
<strong><umb-localize key="visuallyHiddenTexts_dictionaryItems">Dictionary items</umb-localize>:</strong>
<div id="wrapper">
<div>
<strong><umb-localize key="visuallyHiddenTexts_dictionaryItems">Dictionary items</umb-localize>:</strong>
<div id="item-list">${this.#renderFileContents(this.#fileContent)}</div>
</div>
<div>
<strong><umb-localize key="actions_chooseWhereToImport">Choose where to import</umb-localize>:</strong>
<umb-dictionary-item-input
@change=${this.#onParentChange}
.selectedIds=${this._parentId ? [this._parentId] : []}
max="1"></umb-dictionary-item-input>
</div>
<div id="item-list">${this.#renderFileContents(this.#fileContent)}</div>
${this.#renderNavigate()}
</div>
<div>
<strong><umb-localize key="actions_chooseWhereToImport">Choose where to import</umb-localize>:</strong>
<umb-tree alias="Umb.Tree.Dictionary"></umb-tree>
</div>
${this.#renderNavigate()}
`;
}
#renderNavigate() {
return html`<div>
return html`<div id="nav">
<uui-button label=${this.localize.term('general_import')} look="secondary" @click=${this.#onClear}>
<uui-icon name="icon-arrow-left"></uui-icon>
${this.localize.term('general_back')}
@@ -168,7 +172,7 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
#renderUploadZone() {
return html`<umb-localize key="dictionary_importDictionaryItemHelp"></umb-localize>
<uui-form>
<form id="form" name="form" @submit=${this.#handleSubmit}>
<form id="form" name="form" @submit=${this.#onUpload}>
<uui-form-layout-item>
<uui-label for="file" slot="label" required>${this.localize.term('formFileUpload_pickFile')}</uui-label>
<uui-input-file
@@ -197,6 +201,12 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
#item-list div {
padding-left: 20px;
}
#wrapper {
display: flex;
flex-direction: column;
gap: var(--uui-size-3);
}
`,
];
}

View File

@@ -22,24 +22,12 @@ export default class UmbImportDictionaryEntityAction extends UmbEntityActionBase
}
async execute() {
// TODO: what to do if modal service is not available?
console.log('test');
if (!this.#modalContext) return;
const modalContext = this.#modalContext?.open(UMB_IMPORT_DICTIONARY_MODAL, { unique: this.unique });
const something = await modalContext.onSubmit();
console.log('import', something);
const { parentId, temporaryFileId } = await modalContext.onSubmit();
/*
// TODO: get type from modal result
const { temporaryFileId, parentId } = await modalContext.onSubmit();
if (!temporaryFileId) return;
const result = await this.repository?.import(temporaryFileId, parentId);
// TODO => get location header to route to new item
console.log(result);
*/
await this.repository?.import(temporaryFileId, parentId);
}
}

View File

@@ -1 +1,3 @@
export * from './repository/index.js';
export * from './components/index.js';
import './components/index.js';

View File

@@ -99,7 +99,7 @@ export class UmbDictionaryRepository
return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentId) };
}
async requestItemsLegacy(ids: Array<string>) {
async requestItems(ids: Array<string>) {
await this.#init;
if (!ids) {