diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/import-dictionary-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/import-dictionary-modal.token.ts index 74a101e396..aefea44ec1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/import-dictionary-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/import-dictionary-modal.token.ts @@ -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< diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/dictionary-item-input/dictionary-item-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/dictionary-item-input/dictionary-item-input.context.ts new file mode 100644 index 0000000000..87b963ab7d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/dictionary-item-input/dictionary-item-input.context.ts @@ -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 { + constructor(host: UmbControllerHostElement) { + super(host, 'Umb.Repository.Dictionary', UMB_DICTIONARY_ITEM_PICKER_MODAL); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/dictionary-item-input/dictionary-item-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/dictionary-item-input/dictionary-item-input.element.ts new file mode 100644 index 0000000000..b0860583b5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/dictionary-item-input/dictionary-item-input.element.ts @@ -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 { + return this.#pickerContext.getSelection(); + } + public set selectedIds(ids: Array) { + 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; + + #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` ${repeat( + this._items, + (item) => item.id, + (item) => this._renderItem(item), + )} + ` + : ''} + ${this.#renderAddButton()} + `; + } + + #renderAddButton() { + if (this.max > 0 && this.selectedIds.length >= this.max) return; + return html` this.#pickerContext.openPicker()} + label=${this.localize.term('general_add')}>`; + } + + private _renderItem(item: DictionaryItemItemResponseModel) { + if (!item.id) return; + return html` + + + + this.#pickerContext.requestRemoveItem(item.id!)} + label=${this.localize.term('actions_remove')}> + + + `; + } + + static styles = [ + css` + #add-button { + width: 100%; + } + `, + ]; +} + +export default UmbDictionaryItemInputElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-dictionary-item-input': UmbDictionaryItemInputElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/index.ts new file mode 100644 index 0000000000..2cf0fea3fc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/index.ts @@ -0,0 +1 @@ +export * from './dictionary-item-input/dictionary-item-input.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import-dictionary-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import-dictionary-modal.element.ts index 60a080fdcf..6ba503db31 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import-dictionary-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import-dictionary-modal.element.ts @@ -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; + children: Array; } @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 = []; + #fileContent: Array = []; #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): Array { - const items: Array = []; + #makeDictionaryItems(nodeList: NodeListOf): Array { + const items: Array = []; const list: Array = []; 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< `; } - #renderFileContents(items: Array): any { - return html`${items.map((item: DictionaryItem) => { + #renderFileContents(items: Array): any { + return html`${items.map((item: DictionaryItemPreview) => { return html`${item.name}
${this.#renderFileContents(item.children)}
`; })}`; @@ -136,23 +137,26 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< #renderImportDestination() { return html` -
- Dictionary items: +
+
+ Dictionary items: +
${this.#renderFileContents(this.#fileContent)}
+
+
+ Choose where to import: + +
-
${this.#renderFileContents(this.#fileContent)}
+ ${this.#renderNavigate()}
-
- Choose where to import: - - -
- - ${this.#renderNavigate()} `; } #renderNavigate() { - return html`
+ return html`