From 95aedce2930480629e782fcb00e547066e4a085b Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Tue, 14 Nov 2023 12:54:21 +0100 Subject: [PATCH 01/10] export --- .../entity-actions/export/export.action.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts index e5620a6e0c..fd1d14b982 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts @@ -1,5 +1,5 @@ import { UmbDictionaryRepository } from '../../repository/dictionary.repository.js'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { @@ -31,9 +31,21 @@ export default class UmbExportDictionaryEntityAction extends UmbEntityActionBase const { includeChildren } = await modalContext.onSubmit(); if (includeChildren === undefined) return; + // Export the file const result = await this.repository?.export(this.unique, includeChildren); - // TODO => get location header to route to new item - console.log(result); + const blobContent = await result?.data; + if (!blobContent) return; + + const blob = new Blob([blobContent], { type: 'text/plain' }); + const a = document.createElement('a'); + + const url = window.URL.createObjectURL(blob); + a.href = url; + a.download = `${this.unique}.udt`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); } } From 6afc950c2c97aa75b233ba8948a27008815c4d2c Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:42:27 +0100 Subject: [PATCH 02/10] export make sure it cannot be interrupted --- .../entity-actions/export/export.action.ts | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts index fd1d14b982..e5d80a93f4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts @@ -33,19 +33,34 @@ export default class UmbExportDictionaryEntityAction extends UmbEntityActionBase // Export the file const result = await this.repository?.export(this.unique, includeChildren); - const blobContent = await result?.data; - if (!blobContent) return; - const blob = new Blob([blobContent], { type: 'text/plain' }); - const a = document.createElement('a'); + const promise = new Promise((resolve, reject) => { + if (!blobContent) { + reject(); + return; + } - const url = window.URL.createObjectURL(blob); - a.href = url; - a.download = `${this.unique}.udt`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - window.URL.revokeObjectURL(url); + const blob = new Blob([blobContent], { type: 'text/plain' }); + const a = document.createElement('a'); + + const url = window.URL.createObjectURL(blob); + a.href = url; + a.download = `${this.unique}.udt`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + + resolve(); + }); + + promise + .then(() => { + console.log('success'); + }) + .catch(() => { + console.log('fail'); + }); } } From e99cb8b6e3043f0960eec06792fe196689948656 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:43:43 +0100 Subject: [PATCH 03/10] remove todo --- .../dictionary/entity-actions/export/export.action.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts index e5d80a93f4..ea8b005572 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts @@ -22,12 +22,10 @@ export default class UmbExportDictionaryEntityAction extends UmbEntityActionBase } async execute() { - // TODO: what to do if modal service is not available? if (!this.#modalContext) return; const modalContext = this.#modalContext?.open(UMB_EXPORT_DICTIONARY_MODAL, { unique: this.unique }); - // TODO: get type from modal result const { includeChildren } = await modalContext.onSubmit(); if (includeChildren === undefined) return; From 8a471762d0a1c87d62f6317c533670994b939353 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Wed, 15 Nov 2023 09:15:19 +0100 Subject: [PATCH 04/10] import start --- .../token/import-dictionary-modal.token.ts | 1 + .../import/import-dictionary-modal.element.ts | 122 +++++++++++++++--- .../entity-actions/import/import.action.ts | 8 +- 3 files changed, 115 insertions(+), 16 deletions(-) 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 5890b6188d..74a101e396 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 @@ -7,6 +7,7 @@ export interface UmbImportDictionaryModalData { export interface UmbImportDictionaryModalValue { 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/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 5a3b2b1589..6898989507 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 @@ -7,21 +7,103 @@ import { UmbModalBaseElement, } from '@umbraco-cms/backoffice/modal'; import { ImportDictionaryRequestModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbId } from '@umbraco-cms/backoffice/id'; @customElement('umb-import-dictionary-modal') export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< UmbImportDictionaryModalData, UmbImportDictionaryModalValue > { - static styles = [ - UmbTextStyles, - css` - uui-input { - width: 100%; - } - `, - ]; + @state() + private _parentId?: string; + @state() + private _temporaryFileId?: string; + + @query('#form') + private _form!: HTMLFormElement; + + #handleClose() { + this.modalContext?.reject(); + } + + #submit() { + this._form.requestSubmit(); + } + + #handleSubmit(e: Event) { + e.preventDefault(); + const formData = new FormData(this._form); + const file = formData.get('file'); + + this._temporaryFileId = file ? UmbId.new() : undefined; + + console.log(this._temporaryFileId, file); + + //this.modalContext?.submit({ temporaryFileId: this._temporaryFileId, parentId: this._parentId }); + //this.modalContext?.submit(); + } + + #onFileInput() { + this._form.requestSubmit(); + } + + constructor() { + super(); + } + + connectedCallback(): void { + super.connectedCallback(); + this._parentId = this.data?.unique ?? undefined; + } + + render() { + return html` + + + + ${when( + this._temporaryFileId, + () => this.#renderImportDestination(), + () => this.#renderUploadZone(), + )} + + + + `; + } + + #renderImportDestination() { + return html`cHelp`; + } + + #renderUploadZone() { + return html` +
+ + ${this.localize.term('formFileUpload_pickFile')} + + +
+
`; + } + + /* @query('#form') private _form!: HTMLFormElement; @@ -97,7 +179,7 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< this._selection = element.selection; } */ - + /* #renderUploadView() { return html`

To import a dictionary item, find the ".udt" file on your computer by clicking the "Import" button (you'll be @@ -121,9 +203,10 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< `; } + */ /// TODO => Tree view needs isolation and single-select option - #renderImportView() { + /*#renderImportView() { //TODO: gather this data in some other way, we cannot use the feedback from the server anymore. can we use info about the file directly? or is a change to the end point required? /* if (!this._uploadedDictionary?.dictionaryItems) return; @@ -153,21 +236,30 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< look="primary" @click=${this.#importDictionary}> `; - */ } + */ // TODO => Determine what to display when dictionary import/upload fails - #renderErrorView() { + /*#renderErrorView() { return html`Something went wrong`; - } - + }*/ + /* render() { return html` ${when(this._showUploadView, () => this.#renderUploadView())} ${when(this._showImportView, () => this.#renderImportView())} ${when(this._showErrorView, () => this.#renderErrorView())} `; - } + }*/ + + static styles = [ + UmbTextStyles, + css` + uui-input { + width: 100%; + } + `, + ]; } export default UmbImportDictionaryModalLayout; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import.action.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import.action.ts index 1abf9e025b..92e59f0059 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import.action.ts @@ -1,5 +1,5 @@ import { UmbDictionaryRepository } from '../../repository/dictionary.repository.js'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { @@ -23,10 +23,15 @@ 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); + + /* // TODO: get type from modal result const { temporaryFileId, parentId } = await modalContext.onSubmit(); if (!temporaryFileId) return; @@ -35,5 +40,6 @@ export default class UmbImportDictionaryEntityAction extends UmbEntityActionBase // TODO => get location header to route to new item console.log(result); + */ } } From d73458a820bdf6be9fe92cae318bc6ae679364e1 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:23:11 +0100 Subject: [PATCH 05/10] render preview list of uploaded file --- .../import/import-dictionary-modal.element.ts | 138 ++++++++++++++---- 1 file changed, 111 insertions(+), 27 deletions(-) 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 6898989507..a0bcb510d9 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 { UmbDictionaryRepository } from '../../repository/dictionary.repository.js'; -import { css, html, customElement, query, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { UUIInputFileElement } from '@umbraco-cms/backoffice/external/uui'; +import { css, html, customElement, query, state, when, repeat } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbImportDictionaryModalData, @@ -9,6 +10,11 @@ import { import { ImportDictionaryRequestModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbId } from '@umbraco-cms/backoffice/id'; +interface DictionaryItem { + name: string; + children: Array; +} + @customElement('umb-import-dictionary-modal') export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< UmbImportDictionaryModalData, @@ -23,6 +29,13 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< @query('#form') private _form!: HTMLFormElement; + @query('#file') + private _fileInput!: UUIInputFileElement; + + #fileReader; + + #fileContent: Array = []; + #handleClose() { this.modalContext?.reject(); } @@ -34,22 +47,58 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< #handleSubmit(e: Event) { e.preventDefault(); const formData = new FormData(this._form); - const file = formData.get('file'); + const file = formData.get('file') as File; this._temporaryFileId = file ? UmbId.new() : undefined; - console.log(this._temporaryFileId, file); + this.#fileReader.readAsText(file); //this.modalContext?.submit({ temporaryFileId: this._temporaryFileId, parentId: this._parentId }); //this.modalContext?.submit(); } - #onFileInput() { - this._form.requestSubmit(); + async #onFileInput() { + requestAnimationFrame(() => { + this._form.requestSubmit(); + }); + } + + #onClear() { + this._temporaryFileId = ''; } constructor() { super(); + this.#fileReader = new FileReader(); + this.#fileReader.onload = (e) => { + if (typeof e.target?.result === 'string') { + const fileContent = e.target.result; + this.#dictionaryItemBuilder(fileContent); + } + }; + } + + #dictionaryItemBuilder(htmlString: string) { + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, 'text/html'); + const elements = doc.body.childNodes; + + this.#fileContent = this.#makeDictionaryItems(elements); + this.requestUpdate(); + } + + #makeDictionaryItems(nodeList: NodeListOf): Array { + const items: Array = []; + const list: Array = []; + nodeList.forEach((node) => list.push(node as Element)); + + list.forEach((item) => { + items.push({ + name: item.getAttribute('name') ?? '', + children: this.#makeDictionaryItems(item.childNodes) ?? undefined, + }); + }); + return items; } connectedCallback(): void { @@ -60,8 +109,6 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< render() { return html` - - ${when( this._temporaryFileId, () => this.#renderImportDestination(), @@ -73,34 +120,63 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< type="button" label=${this.localize.term('general_cancel')} @click=${this.#handleClose}> + `; + } + + #renderFileContents(items: Array): any { + return html`${items.map((item: DictionaryItem) => { + return html`${item.name} +

${this.#renderFileContents(item.children)}
`; + })}`; + } + + #renderImportDestination() { + return html` +
+ Dictionary items: + +
${this.#renderFileContents(this.#fileContent)}
+
+
+ Choose where to import: + + Render list here +
+ + ${this.#renderNavigate()} + `; + } + + #renderNavigate() { + return html`
+ + + ${this.localize.term('general_back')} + - `; - } - - #renderImportDestination() { - return html`cHelp`; +
`; } #renderUploadZone() { - return html` -
- - ${this.localize.term('formFileUpload_pickFile')} - - -
-
`; + return html` + +
+ + ${this.localize.term('formFileUpload_pickFile')} + + +
+
`; } /* @@ -258,6 +334,14 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< uui-input { width: 100%; } + #item-list { + padding: var(--uui-size-3) var(--uui-size-4); + border: 1px solid var(--uui-color-border); + border-radius: var(--uui-border-radius); + } + #item-list div { + padding-left: 20px; + } `, ]; } From a62baf4242f3f08d03f26095f56cf893017413c0 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:46:15 +0100 Subject: [PATCH 06/10] fix node issue, show tree, remove old code --- .../import/import-dictionary-modal.element.ts | 159 +----------------- 1 file changed, 7 insertions(+), 152 deletions(-) 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 a0bcb510d9..60a080fdcf 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,6 +1,6 @@ import { UmbDictionaryRepository } from '../../repository/dictionary.repository.js'; import { UUIInputFileElement } from '@umbraco-cms/backoffice/external/uui'; -import { css, html, customElement, query, state, when, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, query, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbImportDictionaryModalData, @@ -90,7 +90,11 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< #makeDictionaryItems(nodeList: NodeListOf): Array { const items: Array = []; const list: Array = []; - nodeList.forEach((node) => list.push(node as Element)); + nodeList.forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + list.push(node as Element); + } + }); list.forEach((item) => { items.push({ @@ -140,7 +144,7 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
Choose where to import: - Render list here +
${this.#renderNavigate()} @@ -179,155 +183,6 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< `; } - /* - @query('#form') - private _form!: HTMLFormElement; - - @state() - private _uploadedDictionaryTempId?: string; - - @state() - private _showUploadView = true; - - @state() - private _showImportView = false; - - @state() - private _showErrorView = false; - - @state() - private _selection: Array = []; - - #detailRepo = new UmbDictionaryRepository(this); - - async #importDictionary() { - if (!this._uploadedDictionaryTempId) return; - - this.modalContext?.submit({ - temporaryFileId: this._uploadedDictionaryTempId, - parentId: this._selection[0], - }); - } - - #handleClose() { - this.modalContext?.reject(); - } - - #submitForm() { - this._form?.requestSubmit(); - } - - async #handleSubmit(e: SubmitEvent) { - e.preventDefault(); - - if (!this._form.checkValidity()) return; - - const formData = new FormData(this._form); - - const uploadData: ImportDictionaryRequestModel = { - temporaryFileId: formData.get('file')?.toString() ?? '', - }; - - // TODO: fix this upload experience. We need to update our form so it gets temporary file id from the server: - const { data } = await this.#detailRepo.upload(uploadData); - - if (!data) return; - - this._uploadedDictionaryTempId = data; - // TODO: We need to find another way to gather the data of the uploaded dictionary, to represent the dictionaryItems? See further below. - //this._uploadedDictionary = data; - - if (!this._uploadedDictionaryTempId) { - this._showErrorView = true; - this._showImportView = false; - return; - } - - this._showErrorView = false; - this._showUploadView = false; - this._showImportView = true; - } - - /* - #handleSelectionChange(e: CustomEvent) { - e.stopPropagation(); - const element = e.target as UmbTreeElement; - this._selection = element.selection; - } - */ - /* - #renderUploadView() { - return html`

- To import a dictionary item, find the ".udt" file on your computer by clicking the "Import" button (you'll be - asked for confirmation on the next screen) -

- -
- - File -
- -
-
-
-
- - `; - } - */ - - /// TODO => Tree view needs isolation and single-select option - /*#renderImportView() { - //TODO: gather this data in some other way, we cannot use the feedback from the server anymore. can we use info about the file directly? or is a change to the end point required? - /* - if (!this._uploadedDictionary?.dictionaryItems) return; - - return html` - Dictionary items -
    - ${repeat( - this._uploadedDictionary.dictionaryItems, - (item) => item.name, - (item) => html`
  • ${item.name}
  • ` - )} -
-
- Choose where to import dictionary items (optional) - - - - - `; - } - */ - - // TODO => Determine what to display when dictionary import/upload fails - /*#renderErrorView() { - return html`Something went wrong`; - }*/ - /* - render() { - return html` - ${when(this._showUploadView, () => this.#renderUploadView())} - ${when(this._showImportView, () => this.#renderImportView())} - ${when(this._showErrorView, () => this.#renderErrorView())} - `; - }*/ - static styles = [ UmbTextStyles, css` From 45a2c0a01d43a500a53324a2a3d9d2cca7011958 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:53:56 +0100 Subject: [PATCH 07/10] export easier readable --- .../entity-actions/export/export.action.ts | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts index ea8b005572..75c0c8e448 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/export/export.action.ts @@ -33,32 +33,19 @@ export default class UmbExportDictionaryEntityAction extends UmbEntityActionBase const result = await this.repository?.export(this.unique, includeChildren); const blobContent = await result?.data; - const promise = new Promise((resolve, reject) => { - if (!blobContent) { - reject(); - return; - } + if (!blobContent) return; + const blob = new Blob([blobContent], { type: 'text/plain' }); + const a = document.createElement('a'); + const url = window.URL.createObjectURL(blob); - const blob = new Blob([blobContent], { type: 'text/plain' }); - const a = document.createElement('a'); + // Download + a.href = url; + a.download = `${this.unique}.udt`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); - const url = window.URL.createObjectURL(blob); - a.href = url; - a.download = `${this.unique}.udt`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - - resolve(); - }); - - promise - .then(() => { - console.log('success'); - }) - .catch(() => { - console.log('fail'); - }); + // Clean up + window.URL.revokeObjectURL(url); } } From d31c7169a7f76681f0e97cca83918dffefebc8be Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:44:00 +0100 Subject: [PATCH 08/10] modal picker for parentid --- .../token/import-dictionary-modal.token.ts | 3 +- .../dictionary-item-input.context.ts | 10 ++ .../dictionary-item-input.element.ts | 149 ++++++++++++++++++ .../dictionary/dictionary/components/index.ts | 1 + .../import/import-dictionary-modal.element.ts | 108 +++++++------ .../entity-actions/import/import.action.ts | 16 +- .../packages/dictionary/dictionary/index.ts | 2 + .../repository/dictionary.repository.ts | 2 +- 8 files changed, 225 insertions(+), 66 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/dictionary-item-input/dictionary-item-input.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/dictionary-item-input/dictionary-item-input.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/components/index.ts 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`
Choose where to import: - + ${ + console.log('load') + // + // + }
${this.#renderNavigate()} From b7cc5671b9626b14dc50ef149a4c4814273ef1ea Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Fri, 17 Nov 2023 13:57:27 +0100 Subject: [PATCH 10/10] import wip --- .../import/import-dictionary-modal.element.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 9e2bf1bc7f..e054fc78de 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 @@ -145,9 +145,11 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
Choose where to import: + Work in progress
${ - console.log('load') - //