drag and drop wip

This commit is contained in:
JesmoDev
2024-09-17 15:26:16 +02:00
parent dc276e3593
commit a051713a01

View File

@@ -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<ToolbarConfig> = [];
#selectedValues: string[] = [];
@state()
_selectedValuesNew: ToolbarConfig[][][] = [[[]]];
protected override async firstUpdated(_changedProperties: PropertyValueMap<unknown>) {
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<void> {
// 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`<div class="selected-row">
${row.map((group, index) => {
return this.#renderGroup(group, index, rowIndex);
})}
<uui-button look="secondary" compact @click=${() => this.#addGroup(rowIndex)}>+</uui-button>
</div>`;
}
#renderGroup(group: ToolbarConfig[], groupIndex: number, rowIndex: number) {
return html`<div
class="selected-group"
umb-data-group=${groupIndex}
umb-data-row=${rowIndex}
@dragover=${this.#onDragOver}
@dragend=${this.#onDragEnd}
@drop=${this.#onDrop}
dropzone="move">
${group.map((item) => {
return html`
<uui-button
draggable="true"
@dragstart=${(e: DragEvent) => this.#onDragStart(e, item.alias)}
compact
look="outline"
class=${item.selected ? 'selected' : ''}
label=${item.label}
.value=${item.alias}
@click=${() => this.#onChange(item)}
><uui-icon .svg=${tinyIconSet?.icons[item.icon ?? 'alignjustify']}></uui-icon
></uui-button>
`;
})}
</div>`;
}
override render() {
return html`<ul>
${repeat(
this._toolbarConfig,
(v) => v.alias,
(v) =>
html`<li>
<uui-checkbox label=${v.label} value=${v.alias} ?checked=${v.selected} @change=${this.onChange}>
<uui-icon .svg=${tinyIconSet?.icons[v.icon ?? 'alignjustify']}></uui-icon>
${v.label}
</uui-checkbox>
</li>`,
)}
</ul>`;
console.log('RENDER');
return html`
<div class="selected-bar">
${repeat(this._selectedValuesNew, (row, index) => this.#renderRow(row, index))}
<uui-button look="secondary" compact @click=${() => this.#addRow()}>+</uui-button>
</div>
<div class="extensions">
${repeat(
this._toolbarItems,
(group) => html`
<p class="group-name">${group.name}</p>
${repeat(
group.items,
(item) =>
html`<uui-button
compact
look="outline"
class=${item.selected ? 'selected' : ''}
label=${item.label}
.value=${item.alias}
@click=${() => this.#onExtensionSelect(item)}
><uui-icon .svg=${tinyIconSet?.icons[item.icon ?? 'alignjustify']}></uui-icon
></uui-button>`,
)}
`,
)}
</div>
`;
}
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;
}
`,
];