}) => {
- this.#toolbarLayout = instance.state;
-
- this.observe(instance.observable, (value) => {
- this._toolbar = value.map((rows) => rows.map((groups) => [...groups]));
- });
- },
- );
-
- setTimeout(() => {
- this._toolbar = this.toStructuredData(this.#testData);
- }, 2000);
+ set value(value: TestServerValue) {
+ // if (this.#originalFormat === value) return;
+ // TODO: also check if the added values have positions, if not, there's no need to update the structured data.
+ this.#originalFormat = value;
+ this._structuredData = this.toStructuredData(value);
}
- private moveItem = (from: [number, number, number], to: [number, number, number]) => {
- const [fromRow, fromGroup, fromItem] = from;
- const [toRow, toGroup, toItem] = to;
+ get value(): TestServerValue {
+ return this.#originalFormat;
+ }
- // Get the item to move from the 'from' position
- const itemToMove = this._toolbar[fromRow][fromGroup][fromItem];
+ @property({ attribute: false })
+ extensionConfigs: Extension[] = [];
- // Remove the item from the original position
- this._toolbar[fromRow][fromGroup].splice(fromItem, 1);
+ //TODO: Use the context again so that we can remove items from the extensions list from here.
- // Insert the item into the new position
- this._toolbar[toRow][toGroup].splice(toItem, 0, itemToMove);
+ @state()
+ _structuredData: string[][][] = [[[]]];
- this.#toolbarLayout?.setValue(this._toolbar);
- };
+ #originalFormat: TestServerValue = [];
- #addGroup = (rowIndex: number, groupIndex: number) => {
- this._toolbar[rowIndex].splice(groupIndex, 0, []);
- this.#toolbarLayout?.setValue(this._toolbar);
- };
+ #currentDragAlias?: string;
- #removeGroup = (rowIndex: number, groupIndex: number) => {
- this._toolbar[rowIndex].splice(groupIndex, 1);
- this.#toolbarLayout?.setValue(this._toolbar);
- };
-
- #addRow = (rowIndex: number) => {
- this._toolbar.splice(rowIndex, 0, [[]]);
- this.#toolbarLayout?.setValue(this._toolbar);
- };
-
- #removeRow = (rowIndex: number) => {
- this._toolbar.splice(rowIndex, 1);
- this.#toolbarLayout?.setValue(this._toolbar);
- };
-
- #onDragStart = (event: DragEvent, pos: [number, number, number]) => {
- event.dataTransfer!.setData('application/json', JSON.stringify(pos));
- event.dataTransfer!.dropEffect = 'move';
+ #onDragStart = (event: DragEvent, alias: string) => {
+ this.#currentDragAlias = alias;
+ event.dataTransfer!.effectAllowed = 'move';
};
#onDragOver = (event: DragEvent) => {
event.preventDefault();
+ event.dataTransfer!.dropEffect = 'move';
+ };
+
+ #onDragEnd = (event: DragEvent) => {
+ event.preventDefault();
+ if (event.dataTransfer?.dropEffect === 'none') {
+ const fromPos = this.#originalFormat.find((item) => item.alias === this.#currentDragAlias)?.position;
+ if (!fromPos) return;
+
+ this.removeItem(fromPos);
+ }
};
#onDrop = (event: DragEvent, toPos: [number, number, number]) => {
event.preventDefault();
+ const fromPos = this.#originalFormat.find((item) => item.alias === this.#currentDragAlias)?.position;
- const fromPos: [number, number, number] = JSON.parse(event.dataTransfer!.getData('application/json') ?? '[0,0,0]');
- this.moveItem(fromPos, toPos);
+ if (fromPos) {
+ this.moveItem(fromPos, toPos);
+ } else if (this.#currentDragAlias) {
+ this.insertItem(this.#currentDragAlias, toPos);
+ }
};
- private renderItem(alias: string, rowIndex: number, groupIndex: number, itemIndex: number) {
- const extension = this.availableExtensions.find((ext) => ext.alias === alias);
- if (!extension) return nothing;
+ private moveItem = (from: [number, number, number], to: [number, number, number]) => {
+ const [rowIndex, groupIndex, itemIndex] = from;
+ // Get the item to move from the 'from' position
+ const itemToMove = this._structuredData[rowIndex][groupIndex][itemIndex];
+
+ // Remove the item from the original position
+ this._structuredData[rowIndex][groupIndex].splice(itemIndex, 1);
+
+ this.insertItem(itemToMove, to);
+ };
+
+ private insertItem = (alias: string, toPos: [number, number, number]) => {
+ const [rowIndex, groupIndex, itemIndex] = toPos;
+ // Insert the item into the new position
+ this._structuredData[rowIndex][groupIndex].splice(itemIndex, 0, alias);
+ this.#updateOriginalFormat();
+
+ this.requestUpdate('_structuredData');
+ this.dispatchEvent(new UmbChangeEvent());
+ };
+
+ private removeItem(from: [number, number, number]) {
+ const [rowIndex, groupIndex, itemIndex] = from;
+ this._structuredData[rowIndex][groupIndex].splice(itemIndex, 1);
+
+ this.#updateOriginalFormat();
+
+ this.requestUpdate('_structuredData');
+ this.dispatchEvent(new UmbChangeEvent());
+ }
+
+ #addGroup = (rowIndex: number, groupIndex: number) => {
+ this._structuredData[rowIndex].splice(groupIndex, 0, []);
+ this.requestUpdate('_structuredData');
+ };
+
+ #removeGroup = (rowIndex: number, groupIndex: number) => {
+ if (rowIndex === 0 && groupIndex === 0) {
+ // Prevent removing the last group
+ this._structuredData[rowIndex][groupIndex] = [];
+ } else {
+ this._structuredData[rowIndex].splice(groupIndex, 1);
+ }
+ this.requestUpdate('_structuredData');
+ this.#updateOriginalFormat();
+ };
+
+ #addRow = (rowIndex: number) => {
+ this._structuredData.splice(rowIndex, 0, [[]]);
+ this.requestUpdate('_structuredData');
+ };
+
+ #removeRow = (rowIndex: number) => {
+ if (rowIndex === 0) {
+ // Prevent removing the last row
+ this._structuredData[rowIndex] = [[]];
+ } else {
+ this._structuredData.splice(rowIndex, 1);
+ }
+ this.requestUpdate('_structuredData');
+ this.#updateOriginalFormat();
+ };
+
+ #updateOriginalFormat() {
+ this.#originalFormat = this.toOriginalFormat(this._structuredData);
+ this.dispatchEvent(new UmbChangeEvent());
+ }
+
+ private renderItem(alias: string) {
+ const extension = this.extensionConfigs.find((ext) => ext.alias === alias);
+ if (!extension) return nothing;
return html` this.#onDragStart(e, [rowIndex, groupIndex, itemIndex])}>
- ${extension.label}
+ @dragend=${this.#onDragEnd}
+ @dragstart=${(e: DragEvent) => this.#onDragStart(e, alias)}>
+
`;
}
private renderGroup(group: string[], rowIndex: number, groupIndex: number) {
- console.log('group', group);
return html`
this.#onDrop(e, [rowIndex, groupIndex, 0])}>
- ${group.map((alias, itemIndex) => this.renderItem(alias, rowIndex, groupIndex, itemIndex))}
- this.#removeGroup(rowIndex, groupIndex)}>X
+ @drop=${(e: DragEvent) => this.#onDrop(e, [rowIndex, groupIndex, group.length])}>
+ ${group.map((alias) => this.renderItem(alias))}
+ this.#removeGroup(rowIndex, groupIndex)}>
+
+
`;
}
@@ -174,17 +176,101 @@ export class UmbTiptapToolbarGroupsConfigurationElement extends UmbLitElement {
return html`
${repeat(row, (group, groupIndex) => this.renderGroup(group, rowIndex, groupIndex))}
- this.#addGroup(rowIndex, row.length)}>+
- this.#removeRow(rowIndex)}>X
+ this.#addGroup(rowIndex, row.length)}>+
+ this.#removeRow(rowIndex)}>
+
+
`;
}
override render() {
- return html`${repeat(this._toolbar, (row, rowIndex) => this.renderRow(row, rowIndex))}
- this.#addRow(this._toolbar.length)}>+ `;
+ return html`
+
+ WIP Feature Rows, groups, and item order have no effect yet.
+ However, adding and removing items from the toolbar is functional. Additionally, hiding items from the toolbar
+ while retaining their functionality by excluding them from the toolbar layout is also functional.
+
+ ${repeat(this._structuredData, (row, rowIndex) => this.renderRow(row, rowIndex))}
+ this.#addRow(this._structuredData.length)}>+
+
+
+
+ ${this.#originalFormat?.filter((item) => !item.position).map((item) => this.renderItem(item.alias))}
+
+ `;
}
+ toStructuredData = (data: TestServerValue) => {
+ if (!data?.length) return [[[]]];
+
+ const structuredData: string[][][] = [[[]]];
+ data.forEach(({ alias, position }) => {
+ if (!position) return;
+
+ const [rowIndex, groupIndex, aliasIndex] = position;
+
+ while (structuredData.length <= rowIndex) {
+ structuredData.push([]);
+ }
+
+ const currentRow = structuredData[rowIndex];
+
+ while (currentRow.length <= groupIndex) {
+ currentRow.push([]);
+ }
+
+ const currentGroup = currentRow[groupIndex];
+
+ currentGroup[aliasIndex] = alias;
+ });
+
+ return structuredData;
+ };
+
+ toOriginalFormat = (structuredData: string[][][]) => {
+ const originalData: TestServerValue = [];
+
+ structuredData.forEach((row, rowIndex) => {
+ row.forEach((group, groupIndex) => {
+ group.forEach((alias, aliasIndex) => {
+ if (alias) {
+ originalData.push({
+ alias,
+ position: [rowIndex, groupIndex, aliasIndex],
+ });
+ }
+ });
+ });
+ });
+
+ // add items from this.#originalFormat only if they are not already in the structured data. and if they have a position property set, unset it.
+ this.#originalFormat.forEach((item) => {
+ if (!originalData.some((i) => i.alias === item.alias)) {
+ originalData.push({
+ alias: item.alias,
+ });
+ }
+ });
+
+ // TODO: this code removes the items completely, while the one above just puts them back into the hidden extensions list. Which one do we prefer?
+ // this.#originalFormat.forEach((item) => {
+ // if (!item.position) {
+ // const exists = originalData.find((i) => i.alias === item.alias);
+ // if (!exists) {
+ // originalData.push(item);
+ // }
+ // }
+ // });
+
+ return originalData;
+ };
+
static override styles = [
UmbTextStyles,
css`
@@ -193,6 +279,13 @@ export class UmbTiptapToolbarGroupsConfigurationElement extends UmbLitElement {
flex-direction: column;
gap: 6px;
}
+ .hidden-extensions {
+ display: flex;
+ gap: 6px;
+ }
+ .hidden-extensions-header {
+ margin-bottom: 3px;
+ }
.row {
position: relative;
display: flex;
@@ -202,36 +295,37 @@ export class UmbTiptapToolbarGroupsConfigurationElement extends UmbLitElement {
position: relative;
display: flex;
gap: 3px;
- border: 1px solid #ccc;
+ border-radius: var(--uui-border-radius);
+ background-color: var(--uui-color-surface-alt);
padding: 6px;
- min-height: 24px;
- min-width: 24px;
+ min-height: 30px;
+ min-width: 30px;
}
.item {
- padding: 3px;
- border: 1px solid #ccc;
- border-radius: 3px;
- background-color: #f9f9f9;
+ padding: var(--uui-size-space-2);
+ border: 1px solid var(--uui-color-border);
+ border-radius: var(--uui-border-radius);
+ background-color: var(--uui-color-surface);
+ cursor: move;
+ display: flex;
+ align-items: baseline;
}
+ .remove-row-button,
+ .remove-group-button {
+ display: none;
+ }
.remove-group-button {
position: absolute;
- top: -4px;
- right: -4px;
- display: none;
- }
- .group:hover .remove-group-button {
- display: block;
+ top: -26px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 1;
}
- .remove-row-button {
- position: absolute;
- left: -25px;
- top: 8px;
- display: none;
- }
- .row:hover .remove-row-button {
- display: block;
+ .row:hover .remove-row-button:not(.hidden),
+ .group:hover .remove-group-button:not(.hidden) {
+ display: flex;
}
`,
];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap-toolbar-groups-configuration2.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap-toolbar-groups-configuration2.element.ts
deleted file mode 100644
index b978935be1..0000000000
--- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap-toolbar-groups-configuration2.element.ts
+++ /dev/null
@@ -1,340 +0,0 @@
-import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
-import { customElement, css, html, property, repeat, nothing, state } from '@umbraco-cms/backoffice/external/lit';
-import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
-import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
-
-type Extension = {
- alias: string;
- label: string;
- icon?: string;
-};
-
-type TestServerValue = Array<{
- alias: string;
- position?: [number, number, number];
-}>;
-
-@customElement('umb-tiptap-toolbar-groups-configuration2')
-export class UmbTiptapToolbarGroupsConfiguration2Element extends UmbLitElement {
- @property({ attribute: false })
- set value(value: TestServerValue) {
- // if (this.#originalFormat === value) return;
- // TODO: also check if the added values have positions, if not, there's no need to update the structured data.
- this.#originalFormat = value;
- this._structuredData = this.toStructuredData(value);
- }
-
- get value(): TestServerValue {
- return this.#originalFormat;
- }
-
- @property({ attribute: false })
- extensionConfigs: Extension[] = [];
-
- //TODO: Use the context again so that we can remove items from the extensions list from here.
-
- @state()
- _structuredData: string[][][] = [[[]]];
-
- #originalFormat: TestServerValue = [];
-
- #currentDragAlias?: string;
-
- #onDragStart = (event: DragEvent, alias: string) => {
- this.#currentDragAlias = alias;
- event.dataTransfer!.effectAllowed = 'move';
- };
-
- #onDragOver = (event: DragEvent) => {
- event.preventDefault();
- event.dataTransfer!.dropEffect = 'move';
- };
-
- #onDragEnd = (event: DragEvent) => {
- event.preventDefault();
- if (event.dataTransfer?.dropEffect === 'none') {
- const fromPos = this.#originalFormat.find((item) => item.alias === this.#currentDragAlias)?.position;
- if (!fromPos) return;
-
- this.removeItem(fromPos);
- }
- };
-
- #onDrop = (event: DragEvent, toPos: [number, number, number]) => {
- event.preventDefault();
- const fromPos = this.#originalFormat.find((item) => item.alias === this.#currentDragAlias)?.position;
-
- if (fromPos) {
- this.moveItem(fromPos, toPos);
- } else if (this.#currentDragAlias) {
- this.insertItem(this.#currentDragAlias, toPos);
- }
- };
-
- private moveItem = (from: [number, number, number], to: [number, number, number]) => {
- const [rowIndex, groupIndex, itemIndex] = from;
-
- // Get the item to move from the 'from' position
- const itemToMove = this._structuredData[rowIndex][groupIndex][itemIndex];
-
- // Remove the item from the original position
- this._structuredData[rowIndex][groupIndex].splice(itemIndex, 1);
-
- this.insertItem(itemToMove, to);
- };
-
- private insertItem = (alias: string, toPos: [number, number, number]) => {
- const [rowIndex, groupIndex, itemIndex] = toPos;
- // Insert the item into the new position
- this._structuredData[rowIndex][groupIndex].splice(itemIndex, 0, alias);
- this.#updateOriginalFormat();
-
- this.requestUpdate('_structuredData');
- this.dispatchEvent(new UmbChangeEvent());
- };
-
- private removeItem(from: [number, number, number]) {
- const [rowIndex, groupIndex, itemIndex] = from;
- this._structuredData[rowIndex][groupIndex].splice(itemIndex, 1);
-
- this.#updateOriginalFormat();
-
- this.requestUpdate('_structuredData');
- this.dispatchEvent(new UmbChangeEvent());
- }
-
- #addGroup = (rowIndex: number, groupIndex: number) => {
- this._structuredData[rowIndex].splice(groupIndex, 0, []);
- this.requestUpdate('_structuredData');
- };
-
- #removeGroup = (rowIndex: number, groupIndex: number) => {
- if (rowIndex === 0 && groupIndex === 0) {
- // Prevent removing the last group
- this._structuredData[rowIndex][groupIndex] = [];
- } else {
- this._structuredData[rowIndex].splice(groupIndex, 1);
- }
- this.requestUpdate('_structuredData');
- this.#updateOriginalFormat();
- };
-
- #addRow = (rowIndex: number) => {
- this._structuredData.splice(rowIndex, 0, [[]]);
- this.requestUpdate('_structuredData');
- };
-
- #removeRow = (rowIndex: number) => {
- if (rowIndex === 0) {
- // Prevent removing the last row
- this._structuredData[rowIndex] = [[]];
- } else {
- this._structuredData.splice(rowIndex, 1);
- }
- this.requestUpdate('_structuredData');
- this.#updateOriginalFormat();
- };
-
- #updateOriginalFormat() {
- this.#originalFormat = this.toOriginalFormat(this._structuredData);
- this.dispatchEvent(new UmbChangeEvent());
- }
-
- private renderItem(alias: string) {
- const extension = this.extensionConfigs.find((ext) => ext.alias === alias);
- if (!extension) return nothing;
- return html` this.#onDragStart(e, alias)}>
-
-
`;
- }
-
- private renderGroup(group: string[], rowIndex: number, groupIndex: number) {
- return html`
- this.#onDrop(e, [rowIndex, groupIndex, group.length])}>
- ${group.map((alias) => this.renderItem(alias))}
- this.#removeGroup(rowIndex, groupIndex)}>
-
-
-
- `;
- }
-
- private renderRow(row: string[][], rowIndex: number) {
- return html`
-
- ${repeat(row, (group, groupIndex) => this.renderGroup(group, rowIndex, groupIndex))}
- this.#addGroup(rowIndex, row.length)}>+
- this.#removeRow(rowIndex)}>
-
-
-
- `;
- }
-
- override render() {
- return html`
-
- WIP Feature Rows, groups, and item order have no effect yet.
- However, adding and removing items from the toolbar is functional. Additionally, hiding items from the toolbar
- while retaining their functionality by excluding them from the toolbar layout is also functional.
-
- ${repeat(this._structuredData, (row, rowIndex) => this.renderRow(row, rowIndex))}
- this.#addRow(this._structuredData.length)}>+
-
-
-
- ${this.#originalFormat?.filter((item) => !item.position).map((item) => this.renderItem(item.alias))}
-
- `;
- }
-
- toStructuredData = (data: TestServerValue) => {
- if (!data?.length) return [[[]]];
-
- const structuredData: string[][][] = [[[]]];
- data.forEach(({ alias, position }) => {
- if (!position) return;
-
- const [rowIndex, groupIndex, aliasIndex] = position;
-
- while (structuredData.length <= rowIndex) {
- structuredData.push([]);
- }
-
- const currentRow = structuredData[rowIndex];
-
- while (currentRow.length <= groupIndex) {
- currentRow.push([]);
- }
-
- const currentGroup = currentRow[groupIndex];
-
- currentGroup[aliasIndex] = alias;
- });
-
- return structuredData;
- };
-
- toOriginalFormat = (structuredData: string[][][]) => {
- const originalData: TestServerValue = [];
-
- structuredData.forEach((row, rowIndex) => {
- row.forEach((group, groupIndex) => {
- group.forEach((alias, aliasIndex) => {
- if (alias) {
- originalData.push({
- alias,
- position: [rowIndex, groupIndex, aliasIndex],
- });
- }
- });
- });
- });
-
- // add items from this.#originalFormat only if they are not already in the structured data. and if they have a position property set, unset it.
- this.#originalFormat.forEach((item) => {
- if (!originalData.some((i) => i.alias === item.alias)) {
- originalData.push({
- alias: item.alias,
- });
- }
- });
-
- // TODO: this code removes the items completely, while the one above just puts them back into the hidden extensions list. Which one do we prefer?
- // this.#originalFormat.forEach((item) => {
- // if (!item.position) {
- // const exists = originalData.find((i) => i.alias === item.alias);
- // if (!exists) {
- // originalData.push(item);
- // }
- // }
- // });
-
- return originalData;
- };
-
- static override styles = [
- UmbTextStyles,
- css`
- :host {
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .hidden-extensions {
- display: flex;
- gap: 6px;
- }
- .hidden-extensions-header {
- margin-bottom: 3px;
- }
- .row {
- position: relative;
- display: flex;
- gap: 12px;
- }
- .group {
- position: relative;
- display: flex;
- gap: 3px;
- border-radius: var(--uui-border-radius);
- background-color: var(--uui-color-surface-alt);
- padding: 6px;
- min-height: 30px;
- min-width: 30px;
- }
- .item {
- padding: var(--uui-size-space-2);
- border: 1px solid var(--uui-color-border);
- border-radius: var(--uui-border-radius);
- background-color: var(--uui-color-surface);
- cursor: move;
- display: flex;
- align-items: baseline;
- }
-
- .remove-row-button,
- .remove-group-button {
- display: none;
- }
- .remove-group-button {
- position: absolute;
- top: -26px;
- left: 50%;
- transform: translateX(-50%);
- z-index: 1;
- }
-
- .row:hover .remove-row-button:not(.hidden),
- .group:hover .remove-group-button:not(.hidden) {
- display: flex;
- }
- `,
- ];
-}
-
-export default UmbTiptapToolbarGroupsConfiguration2Element;
-
-declare global {
- interface HTMLElementTagNameMap {
- 'umb-tiptap-toolbar-groups-configuration2': UmbTiptapToolbarGroupsConfiguration2Element;
- }
-}