Skip to content

Commit 973706c

Browse files
committed
Save
1 parent 53eeece commit 973706c

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed

apps/web/src/app/project/[id]/_components/canvas/frames/frame.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export const Frame = observer(
1212
}) => {
1313
const position = settings.position;
1414
const iframeRef = useRef<HTMLIFrameElement>(null);
15+
16+
// TODO: Dimensions need to be structured in a way where Yjs can track changes. Abstract a FrameState interface.
1517
const [dimensions, setDimensions] = useState(settings.dimension);
1618

1719
return (
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { useEditorEngine } from '@/components/store';
2+
import { getRelativeMousePositionToWebview } from '@/components/store/editor/engine/overlay/utils';
3+
import { EditorMode, MouseAction } from '@onlook/models/editor';
4+
import type { DomElement, ElementPosition } from '@onlook/models/element';
5+
import { cn } from '@onlook/ui/utils';
6+
import throttle from 'lodash/throttle';
7+
import { observer } from 'mobx-react-lite';
8+
import { useCallback, useEffect, useMemo } from 'react';
9+
// import RightClickMenu from '../RightClickMenu';
10+
11+
interface GestureScreenProps {
12+
webviewRef: React.RefObject<Electron.WebviewTag>;
13+
setHovered: React.Dispatch<React.SetStateAction<boolean>>;
14+
isResizing: boolean;
15+
}
16+
17+
const GestureScreen = observer(({ webviewRef, setHovered, isResizing }: GestureScreenProps) => {
18+
const editorEngine = useEditorEngine();
19+
20+
const getWebview = useCallback((): Electron.WebviewTag => {
21+
const webview = webviewRef.current as Electron.WebviewTag | null;
22+
if (!webview) {
23+
throw Error('No webview found');
24+
}
25+
return webview;
26+
}, [webviewRef]);
27+
28+
const getRelativeMousePosition = useCallback(
29+
(e: React.MouseEvent<HTMLDivElement>): ElementPosition => {
30+
const webview = getWebview();
31+
return getRelativeMousePositionToWebview(e, webview);
32+
},
33+
[getWebview],
34+
);
35+
36+
const handleMouseEvent = useCallback(
37+
async (e: React.MouseEvent<HTMLDivElement>, action: MouseAction) => {
38+
const webview = getWebview();
39+
const pos = getRelativeMousePosition(e);
40+
const el: DomElement = await webview.executeJavaScript(
41+
`window.api?.getElementAtLoc(${pos.x}, ${pos.y}, ${action === MouseAction.MOUSE_DOWN || action === MouseAction.DOUBLE_CLICK})`,
42+
);
43+
if (!el) {
44+
return;
45+
}
46+
47+
switch (action) {
48+
case MouseAction.MOVE:
49+
editorEngine.elements.mouseover(el, webview);
50+
if (e.altKey) {
51+
editorEngine.elements.showMeasurement();
52+
} else {
53+
editorEngine.overlay.removeMeasurement();
54+
}
55+
break;
56+
case MouseAction.MOUSE_DOWN:
57+
if (el.tagName.toLocaleLowerCase() === 'body') {
58+
editorEngine.webview.select(webview);
59+
return;
60+
}
61+
// Ignore right-clicks
62+
if (e.button == 2) {
63+
break;
64+
}
65+
if (editorEngine.text.isEditing) {
66+
editorEngine.text.end();
67+
}
68+
if (e.shiftKey) {
69+
editorEngine.elements.shiftClick(el, webview);
70+
} else {
71+
editorEngine.move.start(el, pos, webview);
72+
editorEngine.elements.click([el], webview);
73+
}
74+
break;
75+
case MouseAction.DOUBLE_CLICK:
76+
editorEngine.text.start(el, webview);
77+
break;
78+
}
79+
},
80+
[getWebview, getRelativeMousePosition, editorEngine],
81+
);
82+
83+
const throttledMouseMove = useMemo(
84+
() =>
85+
throttle((e: React.MouseEvent<HTMLDivElement>) => {
86+
if (editorEngine.state.move.isDragging) {
87+
editorEngine.state.move.drag(e, getRelativeMousePosition);
88+
} else if (
89+
editorEngine.state.editorMode === EditorMode.DESIGN ||
90+
((editorEngine.state.editorMode === EditorMode.INSERT_DIV ||
91+
editorEngine.state.editorMode === EditorMode.INSERT_TEXT ||
92+
editorEngine.state.editorMode === EditorMode.INSERT_IMAGE) &&
93+
!editorEngine.insert.isDrawing)
94+
) {
95+
handleMouseEvent(e, MouseAction.MOVE);
96+
} else if (editorEngine.insert.isDrawing) {
97+
editorEngine.insert.draw(e);
98+
}
99+
}, 16),
100+
[editorEngine, getRelativeMousePosition, handleMouseEvent],
101+
);
102+
103+
useEffect(() => {
104+
return () => {
105+
throttledMouseMove.cancel();
106+
};
107+
}, [throttledMouseMove]);
108+
109+
const handleClick = useCallback(
110+
(e: React.MouseEvent<HTMLDivElement>) => {
111+
const webview = getWebview();
112+
editorEngine.webview.deselectAll();
113+
editorEngine.webview.select(webview);
114+
},
115+
[getWebview, editorEngine.webview],
116+
);
117+
118+
function handleDoubleClick(e: React.MouseEvent<HTMLDivElement>) {
119+
if (editorEngine.state.editorMode !== EditorMode.DESIGN) {
120+
return;
121+
}
122+
handleMouseEvent(e, MouseAction.DOUBLE_CLICK);
123+
}
124+
125+
function handleMouseDown(e: React.MouseEvent<HTMLDivElement>) {
126+
if (editorEngine.state.editorMode === EditorMode.DESIGN) {
127+
handleMouseEvent(e, MouseAction.MOUSE_DOWN);
128+
} else if (
129+
editorEngine.state.editorMode === EditorMode.INSERT_DIV ||
130+
editorEngine.state.editorMode === EditorMode.INSERT_TEXT ||
131+
editorEngine.state.editorMode === EditorMode.INSERT_IMAGE
132+
) {
133+
editorEngine.insert.start(e);
134+
}
135+
}
136+
137+
async function handleMouseUp(e: React.MouseEvent<HTMLDivElement>) {
138+
editorEngine.insert.end(e, webviewRef.current);
139+
editorEngine.move.end(e);
140+
}
141+
142+
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
143+
e.preventDefault();
144+
e.stopPropagation();
145+
handleMouseEvent(e, MouseAction.MOVE);
146+
};
147+
148+
const handleDrop = async (e: React.DragEvent<HTMLDivElement>) => {
149+
e.preventDefault();
150+
e.stopPropagation();
151+
152+
try {
153+
const propertiesData = e.dataTransfer.getData('application/json');
154+
if (!propertiesData) {
155+
console.error('No element properties in drag data');
156+
return;
157+
}
158+
159+
const properties = JSON.parse(propertiesData);
160+
161+
if (properties.type === 'image') {
162+
const webview = getWebview();
163+
const dropPosition = getRelativeMousePosition(e);
164+
await editorEngine.insert.insertDroppedImage(webview, dropPosition, properties);
165+
} else {
166+
const webview = getWebview();
167+
const dropPosition = getRelativeMousePosition(e);
168+
await editorEngine.insert.insertDroppedElement(webview, dropPosition, properties);
169+
}
170+
171+
editorEngine.state.editorMode = EditorMode.DESIGN;
172+
} catch (error) {
173+
console.error('drop operation failed:', error);
174+
}
175+
};
176+
177+
const gestureScreenClassName = useMemo(() => {
178+
return cn(
179+
'absolute inset-0 bg-transparent',
180+
editorEngine.state.editorMode === EditorMode.PREVIEW && !isResizing ? 'hidden' : 'visible',
181+
editorEngine.state.editorMode === EditorMode.INSERT_DIV && 'cursor-crosshair',
182+
editorEngine.state.editorMode === EditorMode.INSERT_TEXT && 'cursor-text',
183+
);
184+
}, [editorEngine.state.editorMode, isResizing]);
185+
186+
return (
187+
// <RightClickMenu>
188+
<div
189+
className={gestureScreenClassName}
190+
onClick={handleClick}
191+
onMouseOver={() => setHovered(true)}
192+
onMouseOut={useCallback(() => {
193+
setHovered(false);
194+
editorEngine.elements.clearHoveredElement();
195+
editorEngine.overlay.state.updateHoverRect(null);
196+
}, [editorEngine, setHovered])}
197+
onMouseLeave={handleMouseUp}
198+
onMouseMove={throttledMouseMove}
199+
onMouseDown={handleMouseDown}
200+
onMouseUp={handleMouseUp}
201+
onDoubleClick={handleDoubleClick}
202+
onDragOver={handleDragOver}
203+
onDrop={handleDrop}
204+
></div>
205+
// </RightClickMenu>
206+
);
207+
});
208+
209+
export default GestureScreen;

0 commit comments

Comments
 (0)