diff --git a/apps/studio/electron/preload/webview/elements/move/drag.ts b/apps/studio/electron/preload/webview/elements/move/drag.ts index d180b69f7..073fd1282 100644 --- a/apps/studio/electron/preload/webview/elements/move/drag.ts +++ b/apps/studio/electron/preload/webview/elements/move/drag.ts @@ -19,6 +19,7 @@ export function startDrag(domId: string): number | null { } const htmlChildren = Array.from(parent.children).filter(isValidHtmlElement); const originalIndex = htmlChildren.indexOf(el); + prepareElementForDragging(el); createStub(el); const pos = getAbsolutePosition(el); @@ -44,14 +45,57 @@ export function drag(domId: string, dx: number, dy: number, x: number, y: number el.style.width = styles.width + 1; el.style.height = styles.height + 1; el.style.position = 'fixed'; + el.style.zIndex = '9999'; + + const targetContainer = findTargetContainerAtPoint(x, y, el); + + if (targetContainer) { + moveStub(el, x, y, targetContainer); + } else { + removeStub(); + } +} + +function findTargetContainerAtPoint( + x: number, + y: number, + draggedElement: HTMLElement, +): HTMLElement | null { + draggedElement.style.display = 'none'; + + try { + let element = document.elementFromPoint(x, y) as HTMLElement | null; + while (element) { + const styles = window.getComputedStyle(element); + if ( + (styles.display === 'flex' || + styles.display === 'grid' || + styles.display === 'block') && + element !== draggedElement && + !element.hasAttribute(EditorAttributes.DATA_ONLOOK_DRAGGING) && + !draggedElement.contains(element) && + !isStubElement(element) + ) { + return element; + } + element = element.parentElement; + } + } finally { + draggedElement.style.display = ''; + } + + return null; +} - moveStub(el, x, y); +function isStubElement(element: HTMLElement): boolean { + return element.id === EditorAttributes.ONLOOK_STUB_ID; } export function endDrag(domId: string): { newIndex: number; child: DomElement; parent: DomElement; + oldParent?: DomElement; } | null { const el = elementFromDomId(domId); if (!el) { @@ -60,29 +104,43 @@ export function endDrag(domId: string): { return null; } - const parent = el.parentElement; - if (!parent) { + const originalParent = el.parentElement; + const stub = document.getElementById(EditorAttributes.ONLOOK_STUB_ID); + const stubParent = stub?.parentElement; + + if (!stubParent || !originalParent) { console.warn('End drag parent not found'); cleanUpElementAfterDragging(el); + removeStub(); return null; } - const stubIndex = getCurrentStubIndex(parent, el); + const stubIndex = getCurrentStubIndex(stubParent, el); + cleanUpElementAfterDragging(el); removeStub(); if (stubIndex === -1) { return null; } - - const elementIndex = Array.from(parent.children).indexOf(el); + const elementIndex = Array.from(originalParent.children).indexOf(el); if (stubIndex === elementIndex) { return null; } + + if (stubParent !== originalParent) { + return { + newIndex: stubIndex, + child: getDomElement(el, false), + parent: getDomElement(stubParent, false), + oldParent: getDomElement(originalParent, false), + }; + } + return { newIndex: stubIndex, child: getDomElement(el, false), - parent: getDomElement(parent, false), + parent: getDomElement(stubParent, false), }; } diff --git a/apps/studio/electron/preload/webview/elements/move/index.ts b/apps/studio/electron/preload/webview/elements/move/index.ts index 13515fbce..89471f14e 100644 --- a/apps/studio/electron/preload/webview/elements/move/index.ts +++ b/apps/studio/electron/preload/webview/elements/move/index.ts @@ -2,14 +2,18 @@ import type { DomElement } from '@onlook/models/element'; import { getDomElement } from '../helpers'; import { elementFromDomId, isValidHtmlElement } from '/common/helpers'; -export function moveElement(domId: string, newIndex: number): DomElement | undefined { +export function moveElement( + domId: string, + newIndex: number, + targetDomId: string, + oldParentDomId?: string, +): DomElement | undefined { const el = elementFromDomId(domId) as HTMLElement | null; if (!el) { console.warn(`Move element not found: ${domId}`); return; } - - const movedEl = moveElToIndex(el, newIndex); + const movedEl = moveElToIndex(el, newIndex, targetDomId, oldParentDomId); if (!movedEl) { console.warn(`Failed to move element: ${domId}`); return; @@ -31,20 +35,34 @@ export function getElementIndex(domId: string): number { return index; } -export function moveElToIndex(el: HTMLElement, newIndex: number): HTMLElement | undefined { - const parent = el.parentElement; - if (!parent) { - console.warn('Parent not found'); +export function moveElToIndex( + el: HTMLElement, + newIndex: number, + targetDomId: string, + oldParentDomId?: string, +): HTMLElement | undefined { + const targetEl = elementFromDomId(targetDomId) as HTMLElement | null; + if (!targetEl) { + console.warn(`Target element not found: ${targetDomId}`); return; } - parent.removeChild(el); - if (newIndex >= parent.children.length) { - parent.appendChild(el); + const oldParentEl = oldParentDomId + ? (elementFromDomId(oldParentDomId) as HTMLElement | null) + : null; + + if (oldParentEl) { + oldParentEl.removeChild(el); + } else { + el.parentElement?.removeChild(el); + } + + if (newIndex >= targetEl.children.length) { + targetEl.appendChild(el); return el; } - const referenceNode = parent.children[newIndex]; - parent.insertBefore(el, referenceNode); + const referenceNode = targetEl.children[newIndex]; + targetEl.insertBefore(el, referenceNode); return el; } diff --git a/apps/studio/electron/preload/webview/elements/move/stub.ts b/apps/studio/electron/preload/webview/elements/move/stub.ts index 714f69b81..76f7d4b22 100644 --- a/apps/studio/electron/preload/webview/elements/move/stub.ts +++ b/apps/studio/electron/preload/webview/elements/move/stub.ts @@ -22,31 +22,25 @@ export function createStub(el: HTMLElement) { document.body.appendChild(stub); } -export function moveStub(el: HTMLElement, x: number, y: number) { +export function moveStub(el: HTMLElement, x: number, y: number, targetContainer: HTMLElement) { const stub = document.getElementById(EditorAttributes.ONLOOK_STUB_ID); if (!stub) { return; } - const parent = el.parentElement; - if (!parent) { - return; - } - - let displayDirection = el.getAttribute(EditorAttributes.DATA_ONLOOK_DRAG_DIRECTION); - if (!displayDirection) { - displayDirection = getDisplayDirection(parent); - } + const displayDirection = getDisplayDirection(targetContainer); - // Check if the parent is using grid layout - const parentStyle = window.getComputedStyle(parent); - const isGridLayout = parentStyle.display === 'grid'; + // Check if the target container is using grid layout + const containerStyle = window.getComputedStyle(targetContainer); + const isGridLayout = containerStyle.display === 'grid'; - const siblings = Array.from(parent.children).filter((child) => child !== el && child !== stub); + const siblings = Array.from(targetContainer.children).filter( + (child) => child !== el && child !== stub, + ); let insertionIndex; if (isGridLayout) { - insertionIndex = findGridInsertionIndex(parent, siblings, x, y); + insertionIndex = findGridInsertionIndex(targetContainer, siblings, x, y); } else { insertionIndex = findFlexBlockInsertionIndex( siblings, @@ -60,9 +54,9 @@ export function moveStub(el: HTMLElement, x: number, y: number) { // Append element at the insertion index if (insertionIndex >= siblings.length) { - parent.appendChild(stub); + targetContainer.appendChild(stub); } else { - parent.insertBefore(stub, siblings[insertionIndex]); + targetContainer.insertBefore(stub, siblings[insertionIndex]); } stub.style.display = 'block'; diff --git a/apps/studio/electron/preload/webview/events/index.ts b/apps/studio/electron/preload/webview/events/index.ts index e68f1265f..0f69110bd 100644 --- a/apps/studio/electron/preload/webview/events/index.ts +++ b/apps/studio/electron/preload/webview/events/index.ts @@ -5,6 +5,7 @@ import type { Change, GroupContainer, ImageContentData, + IndexActionLocation, } from '@onlook/models/actions'; import { WebviewChannels } from '@onlook/models/constants'; import { ipcRenderer } from 'electron'; @@ -67,13 +68,22 @@ function listenForEditEvents() { }); ipcRenderer.on(WebviewChannels.MOVE_ELEMENT, (_, data) => { - const { domId, newIndex } = data as { + const { domId, location } = data as { domId: string; - newIndex: number; + location: IndexActionLocation; }; - const domEl = moveElement(domId, newIndex); - if (domEl) { - publishMoveElement(domEl); + const { index, targetDomId, oldParentDomId } = location; + + if (oldParentDomId) { + const domEl = moveElement(domId, index, targetDomId, oldParentDomId); + if (domEl) { + publishMoveElement(domEl); + } + } else { + const domEl = moveElement(domId, index, targetDomId); + if (domEl) { + publishMoveElement(domEl); + } } }); diff --git a/apps/studio/src/lib/editor/engine/action/index.ts b/apps/studio/src/lib/editor/engine/action/index.ts index 3911bf36e..a14634005 100644 --- a/apps/studio/src/lib/editor/engine/action/index.ts +++ b/apps/studio/src/lib/editor/engine/action/index.ts @@ -130,7 +130,7 @@ export class ActionManager { } sendToWebview(webview, WebviewChannels.MOVE_ELEMENT, { domId: target.domId, - newIndex: location.index, + location, }); }); } diff --git a/apps/studio/src/lib/editor/engine/code/index.ts b/apps/studio/src/lib/editor/engine/code/index.ts index 8950dba46..49001dbb0 100644 --- a/apps/studio/src/lib/editor/engine/code/index.ts +++ b/apps/studio/src/lib/editor/engine/code/index.ts @@ -2,6 +2,7 @@ import type { ProjectsManager } from '@/lib/projects'; import { invokeMainChannel, sendAnalytics, sendToWebview } from '@/lib/utils'; import type { Action, + CodeInsert, CodeInsertImage, CodeRemoveImage, EditTextAction, @@ -211,16 +212,51 @@ export class CodeManager { continue; } - const movedEl: CodeMove = { - oid: target.oid, - type: CodeActionType.MOVE, - location, - }; - - const request = await getOrCreateCodeDiffRequest(location.targetOid, oidToCodeChange); - request.structureChanges.push(movedEl); + if (!location.oldParentOid) { + const movedEl: CodeMove = { + oid: target.oid, + type: CodeActionType.MOVE, + location, + }; + + const request = await getOrCreateCodeDiffRequest( + location.targetOid, + oidToCodeChange, + ); + request.structureChanges.push(movedEl); + } else { + // TODO: Handle moving from one parent to another + // 1. Remove from old parent + const removeRequest = await getOrCreateCodeDiffRequest( + location.oldParentOid, + oidToCodeChange, + ); + removeRequest.structureChanges.push({ + oid: target.oid, + type: CodeActionType.REMOVE, + }); + + // 2. Add to new parent + // This code still wrong since I can not get full element + + const addRequest = await getOrCreateCodeDiffRequest( + location.targetOid, + oidToCodeChange, + ); + addRequest.structureChanges.push({ + oid: target.oid, + type: CodeActionType.INSERT, + location, + children: [], // This is wrong + tagName: '', // This is wrong + attributes: {}, // This is wrong + textContent: '', + pasteParams: null, + }); + } } + console.log('oidToCodeChange', JSON.stringify(Array.from(oidToCodeChange.values()))); await this.getAndWriteCodeDiff(Array.from(oidToCodeChange.values())); } diff --git a/apps/studio/src/lib/editor/engine/move/index.ts b/apps/studio/src/lib/editor/engine/move/index.ts index cd7d5eaef..25a110b1b 100644 --- a/apps/studio/src/lib/editor/engine/move/index.ts +++ b/apps/studio/src/lib/editor/engine/move/index.ts @@ -81,12 +81,13 @@ export class MoveManager { newIndex: number; child: DomElement; parent: DomElement; + oldParent?: DomElement; } | null = await webview.executeJavaScript( `window.api?.endDrag('${this.dragTarget.domId}')`, ); if (res) { - const { newIndex, child, parent } = res; + const { newIndex, child, parent, oldParent } = res; if (newIndex !== this.originalIndex) { const moveAction = this.createMoveAction( webview.id, @@ -94,6 +95,7 @@ export class MoveManager { parent, newIndex, this.originalIndex, + oldParent, ); this.editorEngine.action.run(moveAction); } @@ -175,6 +177,7 @@ export class MoveManager { parent: DomElement, newIndex: number, originalIndex: number, + oldParent?: DomElement, ): MoveElementAction { return { type: 'move-element', @@ -184,6 +187,8 @@ export class MoveManager { targetOid: parent.instanceId || parent.oid, index: newIndex, originalIndex: originalIndex, + oldParentDomId: oldParent?.domId, + oldParentOid: oldParent?.instanceId || oldParent?.oid, }, targets: [ { diff --git a/packages/models/src/actions/location.ts b/packages/models/src/actions/location.ts index ca0396d96..9c3b40f95 100644 --- a/packages/models/src/actions/location.ts +++ b/packages/models/src/actions/location.ts @@ -4,6 +4,8 @@ const BaseActionLocationSchema = z.object({ type: z.enum(['prepend', 'append']), targetDomId: z.string(), targetOid: z.string().nullable(), + oldParentDomId: z.string().optional(), + oldParentOid: z.string().optional(), }); export const IndexActionLocationSchema = BaseActionLocationSchema.extend({