import works

This commit is contained in:
Lone Iversen
2023-11-22 21:46:13 +01:00
parent d590a452fa
commit 8a871bb3bc
8 changed files with 88 additions and 199 deletions

View File

@@ -1,3 +1,4 @@
import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export interface UmbImportDictionaryModalData {
@@ -5,7 +6,7 @@ export interface UmbImportDictionaryModalData {
}
export interface UmbImportDictionaryModalValue {
temporaryFileId: string;
entityItems: Array<EntityTreeItemResponseModel>;
parentId?: string;
}

View File

@@ -1,10 +0,0 @@
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

@@ -1,149 +0,0 @@
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

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

View File

@@ -1,5 +1,5 @@
import '../../components/dictionary-item-input/dictionary-item-input.element.js';
import UmbDictionaryItemInputElement from '../../components/dictionary-item-input/dictionary-item-input.element.js';
import { UMB_DICTIONARY_TREE_ALIAS } from '../../tree/manifests.js';
import { UmbDictionaryRepository } from '../../repository/dictionary.repository.js';
import { css, html, customElement, query, state, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
@@ -8,11 +8,13 @@ import {
UmbImportDictionaryModalValue,
UmbModalBaseElement,
} from '@umbraco-cms/backoffice/modal';
import { ImportDictionaryRequestModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbId } from '@umbraco-cms/backoffice/id';
import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbTreeElement } from '@umbraco-cms/backoffice/tree';
interface DictionaryItemPreview {
name: string;
id: string;
children: Array<DictionaryItemPreview>;
}
@@ -25,32 +27,64 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
private _parentId?: string;
@state()
private _temporaryFileId?: string;
private _temporaryFileId = '';
@query('#form')
private _form!: HTMLFormElement;
@query('umb-tree')
private _treeElement?: UmbTreeElement;
#fileReader;
#fileNodes!: NodeListOf<ChildNode>;
#fileContent: Array<DictionaryItemPreview> = [];
#dictionaryRepository: UmbDictionaryRepository;
#handleClose() {
this.modalContext?.reject();
}
#submit() {
// 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 });
#createTreeEntitiesFromTempFile(): Array<EntityTreeItemResponseModel> {
const data: Array<EntityTreeItemResponseModel> = [];
const list = this.#dictionaryPreviewItemBuilder(this.#fileNodes);
const scaffold = (items: Array<DictionaryItemPreview>, parentId?: string) => {
items.forEach((item) => {
data.push({
id: item.id,
name: item.name,
type: 'dictionary-item',
hasChildren: item.children.length ? true : false,
parentId: parentId,
});
scaffold(item.children, item.id);
});
};
scaffold(list, this._parentId);
return data;
}
async #submit() {
const { error } = await this.#dictionaryRepository.import(this._temporaryFileId, this._parentId);
if (error) return;
this.modalContext?.submit({ entityItems: this.#createTreeEntitiesFromTempFile(), parentId: this._parentId });
}
constructor() {
super();
this.#dictionaryRepository = new UmbDictionaryRepository(this);
this.#fileReader = new FileReader();
this.#fileReader.onload = (e) => {
if (typeof e.target?.result === 'string') {
const fileContent = e.target.result;
this.#dictionaryItemBuilder(fileContent);
this.#dictionaryPreviewBuilder(fileContent);
}
};
}
@@ -60,16 +94,17 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
this._parentId = this.data?.unique ?? undefined;
}
#dictionaryItemBuilder(htmlString: string) {
#dictionaryPreviewBuilder(htmlString: string) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/xml');
const elements = doc.childNodes;
this.#fileNodes = elements;
this.#fileContent = this.#makeDictionaryItems(elements);
this.#fileContent = this.#dictionaryPreviewItemBuilder(elements);
this.requestUpdate();
}
#makeDictionaryItems(nodeList: NodeListOf<ChildNode>): Array<DictionaryItemPreview> {
#dictionaryPreviewItemBuilder(nodeList: NodeListOf<ChildNode>): Array<DictionaryItemPreview> {
const items: Array<DictionaryItemPreview> = [];
const list: Array<Element> = [];
nodeList.forEach((node) => {
@@ -81,25 +116,29 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
list.forEach((item) => {
items.push({
name: item.getAttribute('Name') ?? '',
children: this.#makeDictionaryItems(item.childNodes) ?? undefined,
id: item.getAttribute('Key') ?? '',
children: this.#dictionaryPreviewItemBuilder(item.childNodes) ?? undefined,
});
});
return items;
}
#onUpload(e: Event) {
async #onUpload(e: Event) {
e.preventDefault();
const formData = new FormData(this._form);
const file = formData.get('file') as Blob;
const file = formData.get('file') as File;
if (!file) throw new Error('File is missing');
this.#fileReader.readAsText(file);
this._temporaryFileId = file ? UmbId.new() : undefined;
this._temporaryFileId = UmbId.new();
this.#dictionaryRepository.upload(this._temporaryFileId, file);
}
#onParentChange(event: CustomEvent) {
this._parentId = (event.target as UmbDictionaryItemInputElement).selectedIds[0] || undefined;
//console.log((event.target as UmbDictionaryItemInputElement).selectedIds[0] || undefined);
#onParentChange() {
this._parentId = this._treeElement?.selection[0] ?? undefined;
}
async #onFileInput() {
@@ -145,16 +184,15 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
</div>
<div>
<strong><umb-localize key="actions_chooseWhereToImport">Choose where to import</umb-localize>:</strong>
Work in progress<br />
${
this._parentId
// TODO
// <umb-dictionary-item-input
// @change=${this.#onParentChange}
// .selectedIds=${this._parentId ? [this._parentId] : []}
// max="1">
// </umb-dictionary-item-input>
}
<br />parentId:
<umb-tree
?hide-tree-root=${true}
?multiple=${false}
alias=${UMB_DICTIONARY_TREE_ALIAS}
@selection-change=${this.#onParentChange}
.selection=${[this._parentId ?? '']}
selectable></umb-tree>
</div>
${this.#renderNavigate()}

View File

@@ -7,11 +7,13 @@ import {
UMB_MODAL_MANAGER_CONTEXT_TOKEN,
UMB_IMPORT_DICTIONARY_MODAL,
} from '@umbraco-cms/backoffice/modal';
import { UMB_DICTIONARY_TREE_STORE_CONTEXT, UmbDictionaryTreeStore } from '@umbraco-cms/backoffice/dictionary';
export default class UmbImportDictionaryEntityAction extends UmbEntityActionBase<UmbDictionaryRepository> {
static styles = [UmbTextStyles];
#modalContext?: UmbModalManagerContext;
#treeStore?: UmbDictionaryTreeStore;
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
super(host, repositoryAlias, unique);
@@ -19,6 +21,9 @@ export default class UmbImportDictionaryEntityAction extends UmbEntityActionBase
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => {
this.#modalContext = instance;
});
this.consumeContext(UMB_DICTIONARY_TREE_STORE_CONTEXT, (instance) => {
this.#treeStore = instance;
});
}
async execute() {
@@ -26,8 +31,12 @@ export default class UmbImportDictionaryEntityAction extends UmbEntityActionBase
const modalContext = this.#modalContext?.open(UMB_IMPORT_DICTIONARY_MODAL, { unique: this.unique });
const { parentId, temporaryFileId } = await modalContext.onSubmit();
const { entityItems, parentId } = await modalContext.onSubmit();
await this.repository?.import(temporaryFileId, parentId);
if (!entityItems?.length) return;
this.#treeStore?.appendItems(entityItems);
if (parentId) this.#treeStore?.updateItem(parentId, { hasChildren: true });
}
}

View File

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

View File

@@ -6,11 +6,11 @@ import { UmbDetailRepository } from '@umbraco-cms/backoffice/repository';
import {
CreateDictionaryItemRequestModel,
DictionaryOverviewResponseModel,
ImportDictionaryRequestModel,
UpdateDictionaryItemRequestModel,
} from '@umbraco-cms/backoffice/backend-api';
import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import { UmbTemporaryFileRepository } from '@umbraco-cms/backoffice/temporary-file';
export class UmbDictionaryRepository
extends UmbBaseController
@@ -30,6 +30,8 @@ export class UmbDictionaryRepository
#detailSource: UmbDictionaryDetailServerDataSource;
#detailStore?: UmbDictionaryStore;
#temporaryFileRepository: UmbTemporaryFileRepository;
#notificationContext?: UmbNotificationContext;
constructor(host: UmbControllerHostElement) {
@@ -37,6 +39,7 @@ export class UmbDictionaryRepository
// TODO: figure out how spin up get the correct data source
this.#detailSource = new UmbDictionaryDetailServerDataSource(this);
this.#temporaryFileRepository = new UmbTemporaryFileRepository(host);
this.#init = Promise.all([
this.consumeContext(UMB_DICTIONARY_STORE_CONTEXT_TOKEN, (instance) => {
@@ -92,6 +95,7 @@ export class UmbDictionaryRepository
async delete(id: string) {
await this.#init;
await this.#treeStore?.removeItem(id);
return this.#detailSource.delete(id);
}
@@ -154,14 +158,12 @@ export class UmbDictionaryRepository
return this.#detailSource.import(temporaryFileId, parentId);
}
async upload(formData: ImportDictionaryRequestModel) {
async upload(UmbId: string, file: File) {
await this.#init;
if (!UmbId) throw new Error('UmbId is missing');
if (!file) throw new Error('File is missing');
if (!formData) {
throw new Error('Form data is missing');
}
return this.#detailSource.upload(formData);
return this.#temporaryFileRepository.upload(UmbId, file);
}
// TODO => temporary only, until languages data source exists, or might be