From a051713a0144a8cd37ead79fd54d0f2bf1056be9 Mon Sep 17 00:00:00 2001 From: JesmoDev <26099018+JesmoDev@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:26:16 +0200 Subject: [PATCH] drag and drop wip --- ...ui-tiptap-toolbar-configuration.element.ts | 319 ++++++++++++++---- 1 file changed, 258 insertions(+), 61 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/property-editor-ui-tiptap-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/property-editor-ui-tiptap-toolbar-configuration.element.ts index 6524700bb1..de1c6e67ab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/property-editor-ui-tiptap-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/property-editor-ui-tiptap-toolbar-configuration.element.ts @@ -1,14 +1,13 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; -import { customElement, css, html, property, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { customElement, css, html, property, state, repeat, render } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; import { UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; + import { tinymce } from '@umbraco-cms/backoffice/external/tinymce'; const tinyIconSet = tinymce.IconManager.get('default'); @@ -18,8 +17,14 @@ type ToolbarConfig = { label: string; icon?: string; selected: boolean; + group: string; }; +type ToolbarItems = Array<{ + name: string; + items: ToolbarConfig[]; +}>; + /** * @element umb-property-editor-ui-tiptap-toolbar-configuration */ @@ -30,26 +35,55 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement { @property({ attribute: false }) set value(value: string | string[] | null) { - if (!value) return; - - if (typeof value === 'string') { - this.#selectedValues = value.split(',').filter((x) => x.length > 0); - } else if (Array.isArray(value)) { - this.#selectedValues = value; - } else { + if (!value) { this.#selectedValues = []; - return; + } else { + if (typeof value === 'string') { + this.#selectedValues = value.split(',').filter((x) => x.length > 0); + } else if (Array.isArray(value)) { + this.#selectedValues = value; + } else { + this.#selectedValues = []; + } } - // Migrations - if (this.#selectedValues.includes('ace')) { - this.#selectedValues = this.#selectedValues.filter((v) => v !== 'ace'); - this.#selectedValues.push('sourcecode'); - } + this._selectedValuesNew = [ + [ + [ + { + alias: 'undo', + label: 'Undo', + icon: 'undo', + selected: false, + group: 'clipboard', + }, + ], + ], + [[]], + ]; - this._toolbarConfig.forEach((v) => { - v.selected = this.#selectedValues.includes(v.alias); + this.#selectedValues.forEach((alias) => { + const row = Math.floor(Math.random() * 2); + const group = Math.floor(Math.random() * 2); + const item = this._toolbarConfig.find((value) => value.alias === alias); + + if (!item) return; + + // Ensure the row exists + if (!this._selectedValuesNew[row]) { + this._selectedValuesNew[row] = []; // Initialize the row if it doesn't exist + } + + // Ensure the group exists within the row + if (!this._selectedValuesNew[row][group]) { + this._selectedValuesNew[row][group] = []; // Initialize the group if it doesn't exist + } + + // Add the item to the selectedValuesNew array + this._selectedValuesNew[row][group].push(item); }); + + this.requestUpdate('#selectedValuesNew'); } get value(): string[] { return this.#selectedValues; @@ -59,10 +93,16 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement config?: UmbPropertyEditorConfigCollection; @state() - private _toolbarConfig: ToolbarConfig[] = []; + private _toolbarItems: ToolbarItems = []; + + @state() + private _toolbarConfig: Array = []; #selectedValues: string[] = []; + @state() + _selectedValuesNew: ToolbarConfig[][][] = [[[]]]; + protected override async firstUpdated(_changedProperties: PropertyValueMap) { super.firstUpdated(_changedProperties); @@ -73,68 +113,225 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement }); }); - await this.getToolbarPlugins(); + const grouped = this._toolbarConfig.reduce((acc: any, item) => { + const group = item.group || 'miscellaneous'; // Assign to "miscellaneous" if no group + + if (!acc[group]) { + acc[group] = []; + } + acc[group].push(item); + return acc; + }, {}); + + this._toolbarItems = Object.keys(grouped).map((group) => ({ + name: group.charAt(0).toUpperCase() + group.slice(1).replace(/-/g, ' '), + items: grouped[group], + })); this.requestUpdate('_toolbarConfig'); } - private async getToolbarPlugins(): Promise { - // Get all the toolbar plugins - const plugin$ = umbExtensionsRegistry.byType('tinyMcePlugin'); + #onExtensionSelect(item: ToolbarConfig, row?: number, group?: number) { + // if no row is provided, add to the last row and last group + if (row === undefined) { + row = this._selectedValuesNew.length - 1; + } - const plugins = await firstValueFrom(plugin$); + // if no group is provided, add to the last group in the row + if (group === undefined) { + group = this._selectedValuesNew[row].length - 1; + } - plugins.forEach((p) => { - // If the plugin has a toolbar, add it to the config - if (p.meta?.toolbar) { - p.meta.toolbar.forEach((t: any) => { - this._toolbarConfig.push({ - alias: t.alias, - label: this.localize.string(t.label), - icon: t.icon ?? 'icon-autofill', - selected: this.value.includes(t.alias), - }); - }); - } - }); + // Add the item to the selectedValuesNew array + this._selectedValuesNew[row][group].push(item); + this.requestUpdate('_selectedValuesNew'); } - private onChange(event: CustomEvent) { - const checkbox = event.target as HTMLInputElement; - const alias = checkbox.value; + #addGroup(row: number) { + this._selectedValuesNew[row].push([]); + this.requestUpdate('_selectedValuesNew'); + } - const value = this._toolbarConfig - .filter((t) => (t.alias !== alias && t.selected) || (t.alias === alias && checkbox.checked)) - .map((v) => v.alias); + #addRow() { + this._selectedValuesNew.push([[]]); + this.requestUpdate('_selectedValuesNew'); + } - this.value = value; + #onChange = (item: ToolbarConfig) => { + const value = this._toolbarItems + .flatMap((group) => + group.items.map((i) => { + if (i.alias === item.alias) { + i.selected = !i.selected; + } + return i.selected ? i.alias : null; + }), + ) + .filter((v): v is string => v !== null); // Ensures we only keep non-null strings + + // If the value array is empty, set this.value to null, otherwise assign the array + this.value = value.length > 0 ? value : null; this.dispatchEvent(new UmbPropertyValueChangeEvent()); + }; + + #onDragStart = (event: DragEvent, alias: string) => { + event.dataTransfer!.setData('text/plain', alias); + event.dataTransfer!.dropEffect = 'move'; + event.dataTransfer!.effectAllowed = 'move'; + }; + + #onDragOver = (event: DragEvent) => { + event.preventDefault(); + const element = event.target as HTMLElement; + if (!element) return; + element.classList.add('drag-over'); + }; + + #onDragEnd(event: DragEvent) { + const element = event.target as HTMLElement; + if (!element) return; + element.classList.remove('drag-over'); + } + + #onDrop(event: DragEvent) { + event.preventDefault(); + const groupElement = event.target as HTMLElement; + if (!groupElement) return; + + groupElement.classList.remove('drag-over'); + + const alias = event.dataTransfer!.getData('text/plain'); + if (!alias) return; + + const item = this._toolbarConfig.find((v) => v.alias === alias); + if (!item) return; + + const rowAttribute = groupElement.getAttribute('umb-data-row'); + const rowIndex = rowAttribute ? Number.parseInt(rowAttribute) : null; + + const groupAttribute = groupElement.getAttribute('umb-data-group'); + const groupIndex = groupAttribute ? Number.parseInt(groupAttribute) : null; + + if (groupIndex === null || rowIndex === null) return; + + // remove alias from selectedValues + this._selectedValuesNew = this._selectedValuesNew.map((row) => + row.map((group) => group.filter((v) => v.alias !== alias)), + ); + + this.#onExtensionSelect(item, rowIndex, groupIndex); + } + + #renderRow(row: ToolbarConfig[][], rowIndex: number) { + return html`
+ ${row.map((group, index) => { + return this.#renderGroup(group, index, rowIndex); + })} + this.#addGroup(rowIndex)}>+ +
`; + } + + #renderGroup(group: ToolbarConfig[], groupIndex: number, rowIndex: number) { + return html`
+ ${group.map((item) => { + return html` + this.#onDragStart(e, item.alias)} + compact + look="outline" + class=${item.selected ? 'selected' : ''} + label=${item.label} + .value=${item.alias} + @click=${() => this.#onChange(item)} + > + `; + })} +
`; } override render() { - return html`
    - ${repeat( - this._toolbarConfig, - (v) => v.alias, - (v) => - html`
  • - - - ${v.label} - -
  • `, - )} -
`; + console.log('RENDER'); + return html` +
+ ${repeat(this._selectedValuesNew, (row, index) => this.#renderRow(row, index))} + this.#addRow()}>+ +
+
+ ${repeat( + this._toolbarItems, + (group) => html` +

${group.name}

+ ${repeat( + group.items, + (item) => + html` this.#onExtensionSelect(item)} + >`, + )} + `, + )} +
+ `; } static override styles = [ UmbTextStyles, css` - ul { - list-style: none; - padding: 0; - margin: 0; + uui-icon { + width: unset; + height: unset; + display: flex; + vertical-align: unset; + } + uui-button.selected { + --uui-button-border-color: var(--uui-color-selected); + --uui-button-border-width: 2px; + } + .selected-bar { + display: flex; + flex-direction: column; + gap: 12px; + } + .selected-row { + display: flex; + gap: 18px; + } + .selected-group { + padding: 6px; + min-width: 12px; + background-color: var(--uui-color-surface-alt); + border-radius: var(--uui-border-radius); + display: flex; + gap: 6px; + } + .selected-group.drag-over uui-button { + pointer-events: none; + } + .extensions { + display: grid; + grid-template-columns: repeat(auto-fit, 36px); + gap: 10px; + } + .group-name { + grid-column: 1 / -1; + margin-bottom: 0; + font-weight: bold; } `, ];