Skip to content

Web version pt.4 #1730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/studio/src/routes/editor/LayersPanel/LayersTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { type NodeApi, Tree, type TreeApi } from 'react-arborist';
import useResizeObserver from 'use-resize-observer';
import RightClickMenu from '../RightClickMenu';
import { RightClickMenu } from '../RightClickMenu';
import TreeNode from './Tree/TreeNode';
import TreeRow from './Tree/TreeRow';

Expand Down
2 changes: 0 additions & 2 deletions apps/studio/src/routes/editor/RightClickMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,5 +227,3 @@ export const RightClickMenu = observer(({ children }: RightClickMenuProps) => {
</ContextMenu>
);
});

export default RightClickMenu;
4 changes: 2 additions & 2 deletions apps/studio/src/routes/editor/WebviewArea/GestureScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { useEditorEngine } from '@/components/Context';
import { getRelativeMousePositionToWebview } from '@/lib/editor/engine/overlay/utils';
import { EditorMode } from '@/lib/models';
import { MouseAction } from '@onlook/models/editor';
import type { DomElement, DropElementProperties, ElementPosition } from '@onlook/models/element';
import type { DomElement, ElementPosition } from '@onlook/models/element';
import { cn } from '@onlook/ui/utils';
import throttle from 'lodash/throttle';
import { observer } from 'mobx-react-lite';
import { useCallback, useEffect, useMemo } from 'react';
import RightClickMenu from '../RightClickMenu';
import { RightClickMenu } from '../RightClickMenu';

interface GestureScreenProps {
webviewRef: React.RefObject<Electron.WebviewTag>;
Expand Down
202 changes: 202 additions & 0 deletions apps/web/src/app/project/[id]/_components/canvas/frame/gesture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { useEditorEngine } from '@/components/store';
import { getRelativeMousePositionToWebview } from '@/components/store/editor/engine/overlay/utils';
import { EditorMode, type MouseAction } from '@onlook/models/editor';
import type { ElementPosition } from '@onlook/models/element';
import { cn } from '@onlook/ui/utils';
import throttle from 'lodash/throttle';
import { observer } from 'mobx-react-lite';
import { useCallback, useEffect, useMemo } from 'react';
import { RightClickMenu } from './right-click';

export const GestureScreen = observer(() => {
const editorEngine = useEditorEngine();
const isResizing = false;

const getWebview = useCallback((): Electron.WebviewTag => {
// const webview = webviewRef.current as Electron.WebviewTag | null;
// if (!webview) {
// throw Error('No webview found');
// }
// return webview;
}, []);

const getRelativeMousePosition = useCallback(
(e: React.MouseEvent<HTMLDivElement>): ElementPosition => {
const webview = getWebview();
return getRelativeMousePositionToWebview(e, webview);
},
[getWebview],
);

const handleMouseEvent = useCallback(
async (e: React.MouseEvent<HTMLDivElement>, action: MouseAction) => {
// const webview = getWebview();
// const pos = getRelativeMousePosition(e);
// const el: DomElement = await webview.executeJavaScript(
// `window.api?.getElementAtLoc(${pos.x}, ${pos.y}, ${action === MouseAction.MOUSE_DOWN || action === MouseAction.DOUBLE_CLICK})`,
// );
// if (!el) {
// return;
// }

// switch (action) {
// case MouseAction.MOVE:
// editorEngine.elements.mouseover(el, webview);
// if (e.altKey) {
// editorEngine.elements.showMeasurement();
// } else {
// editorEngine.overlay.removeMeasurement();
// }
// break;
// case MouseAction.MOUSE_DOWN:
// if (el.tagName.toLocaleLowerCase() === 'body') {
// editorEngine.webview.select(webview);
// return;
// }
// // Ignore right-clicks
// if (e.button == 2) {
// break;
// }
// if (editorEngine.text.isEditing) {
// editorEngine.text.end();
// }
// if (e.shiftKey) {
// editorEngine.elements.shiftClick(el, webview);
// } else {
// editorEngine.move.start(el, pos, webview);
// editorEngine.elements.click([el], webview);
// }
// break;
// case MouseAction.DOUBLE_CLICK:
// editorEngine.text.start(el, webview);
// break;
// }
},
[getWebview, getRelativeMousePosition, editorEngine],
);

const throttledMouseMove = useMemo(
() =>
throttle((e: React.MouseEvent<HTMLDivElement>) => {
// if (editorEngine.state.move.isDragging) {
// editorEngine.state.move.drag(e, getRelativeMousePosition);
// } else if (
// editorEngine.state.editorMode === EditorMode.DESIGN ||
// ((editorEngine.state.editorMode === EditorMode.INSERT_DIV ||
// editorEngine.state.editorMode === EditorMode.INSERT_TEXT ||
// editorEngine.state.editorMode === EditorMode.INSERT_IMAGE) &&
// !editorEngine.insert.isDrawing)
// ) {
// handleMouseEvent(e, MouseAction.MOVE);
// } else if (editorEngine.insert.isDrawing) {
// editorEngine.insert.draw(e);
// }
}, 16),
[editorEngine, getRelativeMousePosition, handleMouseEvent],
);

useEffect(() => {
return () => {
throttledMouseMove.cancel();
};
}, [throttledMouseMove]);

const handleClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
// const webview = getWebview();
// editorEngine.webview.deselectAll();
// editorEngine.webview.select(webview);
},
[getWebview, editorEngine.webview],
);

function handleDoubleClick(e: React.MouseEvent<HTMLDivElement>) {
// if (editorEngine.state.editorMode !== EditorMode.DESIGN) {
// return;
// }
// handleMouseEvent(e, MouseAction.DOUBLE_CLICK);
}

function handleMouseDown(e: React.MouseEvent<HTMLDivElement>) {
// if (editorEngine.state.editorMode === EditorMode.DESIGN) {
// handleMouseEvent(e, MouseAction.MOUSE_DOWN);
// } else if (
// editorEngine.state.editorMode === EditorMode.INSERT_DIV ||
// editorEngine.state.editorMode === EditorMode.INSERT_TEXT ||
// editorEngine.state.editorMode === EditorMode.INSERT_IMAGE
// ) {
// editorEngine.insert.start(e);
// }
}

async function handleMouseUp(e: React.MouseEvent<HTMLDivElement>) {
// editorEngine.insert.end(e, webviewRef.current);
// editorEngine.move.end(e);
}

const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
// e.preventDefault();
// e.stopPropagation();
// handleMouseEvent(e, MouseAction.MOVE);
};

const handleDrop = async (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();

// try {
// const propertiesData = e.dataTransfer.getData('application/json');
// if (!propertiesData) {
// console.error('No element properties in drag data');
// return;
// }

// const properties = JSON.parse(propertiesData);

// if (properties.type === 'image') {
// const webview = getWebview();
// const dropPosition = getRelativeMousePosition(e);
// await editorEngine.insert.insertDroppedImage(webview, dropPosition, properties);
// } else {
// const webview = getWebview();
// const dropPosition = getRelativeMousePosition(e);
// await editorEngine.insert.insertDroppedElement(webview, dropPosition, properties);
// }

// editorEngine.state.editorMode = EditorMode.DESIGN;
// } catch (error) {
// console.error('drop operation failed:', error);
// }
};

const gestureScreenClassName = useMemo(() => {
return cn(
'absolute inset-0 bg-transparent',
editorEngine.state.editorMode === EditorMode.PREVIEW && !isResizing ? 'hidden' : 'visible',
editorEngine.state.editorMode === EditorMode.INSERT_DIV && 'cursor-crosshair',
editorEngine.state.editorMode === EditorMode.INSERT_TEXT && 'cursor-text',
);
}, [editorEngine.state.editorMode, isResizing]);

const handleMouseOut = () => {
editorEngine.elements.clearHoveredElement();
editorEngine.overlay.state.updateHoverRect(null);
}

return (
<RightClickMenu>
<div
className={gestureScreenClassName}
onClick={handleClick}
onMouseOut={handleMouseOut}
onMouseLeave={handleMouseUp}
onMouseMove={throttledMouseMove}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onDoubleClick={handleDoubleClick}
onDragOver={handleDragOver}
onDrop={handleDrop}
></div>
</RightClickMenu>
);
});
30 changes: 30 additions & 0 deletions apps/web/src/app/project/[id]/_components/canvas/frame/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { FrameType, type Frame, type WebFrame } from "@onlook/models";
import { observer } from "mobx-react-lite";
import { GestureScreen } from './gesture';
import { ResizeHandles } from './resize-handles';
import { TopBar } from "./top-bar";
import { WebFrameComponent } from "./web-frame";

export const FrameView = observer(
({
frame,
}: {
frame: Frame;
}) => {
return (
<div
className="flex flex-col fixed"
style={{ transform: `translate(${frame.position.x}px, ${frame.position.y}px)` }}
>
<TopBar frame={frame}>
</TopBar>
<div className="relative">
<ResizeHandles frame={frame} />
{frame.type === FrameType.WEB && <WebFrameComponent frame={frame as WebFrame} />}
<GestureScreen />
{/* {domFailed && shouldShowDomFailed && renderNotRunning()} */}
</div>

</div>
);
});
Loading