Skip to content

Commit 3aa26c9

Browse files
authored
Web version pt.5 (#1747)
* add left panel * update ui * add bottom bar * Switch to dark mode
1 parent 9e422d9 commit 3aa26c9

File tree

19 files changed

+567
-188
lines changed

19 files changed

+567
-188
lines changed

apps/studio/src/lib/user/subscription.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class SubscriptionManager {
1313
}
1414

1515
private restoreCachedPlan() {
16-
const cachedPlan = localStorage.getItem('currentPlan');
16+
const cachedPlan = localStorage?.getItem('currentPlan');
1717
this.plan = (cachedPlan as UsagePlanType) || UsagePlanType.BASIC;
1818
}
1919

apps/web/client/src/app/layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@ const inter = Inter({
2121
export default async function RootLayout({
2222
children
2323
}: {
24-
children: React.ReactNode;
24+
children: React.ReactNode
2525
}) {
2626
const locale = await getLocale();
2727

2828
return (
29-
<html lang={locale} className={`${inter.variable}`}>
29+
<html lang={locale} className={`${inter.variable} dark`}>
3030
<body>
3131
<TRPCReactProvider>
3232
<NextIntlClientProvider>
33-
{children as any}
33+
{children}
3434
{/* <Modals /> */}
3535
</NextIntlClientProvider>
3636
</TRPCReactProvider>
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { Hotkey } from '@/components/hotkey';
2+
import { useEditorEngine } from '@/components/store';
3+
import { EditorMode } from '@onlook/models';
4+
import type { DropElementProperties } from '@onlook/models/element';
5+
import { HotkeyLabel } from '@onlook/ui-v4/hotkey-label';
6+
import { Icons } from '@onlook/ui-v4/icons';
7+
import { ToggleGroup, ToggleGroupItem } from '@onlook/ui-v4/toggle-group';
8+
import { Tooltip, TooltipContent, TooltipTrigger } from '@onlook/ui-v4/tooltip';
9+
import { observer } from 'mobx-react-lite';
10+
import { AnimatePresence, motion } from 'motion/react';
11+
import { useTranslations } from 'next-intl';
12+
import { useState } from 'react';
13+
14+
// import Terminal from './Terminal';
15+
16+
const TOOLBAR_ITEMS = ({ t }: { t: (key: string) => string }) => [
17+
{
18+
mode: EditorMode.DESIGN,
19+
icon: Icons.CursorArrow,
20+
hotkey: Hotkey.SELECT,
21+
disabled: false,
22+
draggable: false,
23+
label: t('editor.toolbar.tools.select.name'),
24+
tooltip: t('editor.toolbar.tools.select.tooltip'),
25+
},
26+
{
27+
mode: EditorMode.PAN,
28+
icon: Icons.Hand,
29+
hotkey: Hotkey.PAN,
30+
disabled: false,
31+
draggable: false,
32+
label: t('editor.toolbar.tools.pan.name'),
33+
tooltip: t('editor.toolbar.tools.pan.tooltip'),
34+
},
35+
{
36+
mode: EditorMode.INSERT_DIV,
37+
icon: Icons.Square,
38+
hotkey: Hotkey.INSERT_DIV,
39+
disabled: false,
40+
draggable: true,
41+
label: t('editor.toolbar.tools.insertDiv.name'),
42+
tooltip: t('editor.toolbar.tools.insertDiv.tooltip'),
43+
},
44+
{
45+
mode: EditorMode.INSERT_TEXT,
46+
icon: Icons.Text,
47+
hotkey: Hotkey.INSERT_TEXT,
48+
disabled: false,
49+
draggable: true,
50+
label: t('editor.toolbar.tools.insertText.name'),
51+
tooltip: t('editor.toolbar.tools.insertText.tooltip'),
52+
},
53+
];
54+
55+
export const BottomBar = observer(() => {
56+
const t = useTranslations();
57+
const editorEngine = useEditorEngine();
58+
const [mode, setMode] = useState<EditorMode>(editorEngine.state.editorMode);
59+
const [terminalHidden, setTerminalHidden] = useState(true);
60+
61+
const createDragPreview = (properties: DropElementProperties): HTMLElement => {
62+
const preview = document.createElement('div');
63+
Object.assign(preview.style, {
64+
width: '100px',
65+
height: '100px',
66+
display: 'flex',
67+
alignItems: 'center',
68+
justifyContent: 'center',
69+
...properties.styles,
70+
});
71+
72+
if (properties.textContent) {
73+
preview.textContent = properties.textContent;
74+
}
75+
76+
return preview;
77+
};
78+
79+
const handleDragStart = (e: React.DragEvent<HTMLDivElement>, mode: EditorMode) => {
80+
// const properties = editorEngine.insert.getDefaultProperties(mode);
81+
82+
// e.dataTransfer.setData('text/plain', mode);
83+
// e.dataTransfer.setData('application/json', JSON.stringify(properties));
84+
// e.dataTransfer.effectAllowed = 'copy';
85+
86+
// editorEngine.state.editorMode = mode;
87+
88+
// // Disable pointer-events on webviews during drag
89+
// for (const webview of editorEngine.webviews.webviews.values()) {
90+
// webview.webview.style.pointerEvents = 'none';
91+
// }
92+
93+
// const dragPreview = createDragPreview(properties);
94+
// document.body.appendChild(dragPreview);
95+
// e.dataTransfer.setDragImage(dragPreview, 50, 50);
96+
97+
// setTimeout(() => document.body.removeChild(dragPreview), 0);
98+
};
99+
100+
const toolbarItems = TOOLBAR_ITEMS({ t });
101+
102+
return (
103+
<AnimatePresence mode="wait">
104+
{editorEngine.state.editorMode !== EditorMode.PREVIEW && (
105+
<motion.div
106+
initial={{ opacity: 0, y: 20 }}
107+
animate={{ opacity: 1, y: 0 }}
108+
exit={{ opacity: 0, y: 20 }}
109+
className="flex flex-col border p-1 px-1.5 bg-background-secondary/85 dark:bg-background/85 backdrop-blur rounded-lg drop-shadow-xl"
110+
transition={{
111+
type: 'spring',
112+
bounce: 0.1,
113+
duration: 0.4,
114+
stiffness: 200,
115+
damping: 25,
116+
}}
117+
>
118+
{terminalHidden ? (
119+
<motion.div layout className="flex items-center gap-1">
120+
<ToggleGroup
121+
type="single"
122+
value={mode}
123+
onValueChange={(value) => {
124+
if (value) {
125+
editorEngine.state.editorMode = value as EditorMode;
126+
setMode(value as EditorMode);
127+
}
128+
}}
129+
>
130+
{toolbarItems.map((item) => (
131+
<Tooltip key={item.mode}>
132+
<TooltipTrigger asChild>
133+
<div
134+
draggable={item.draggable}
135+
onDragStart={(e) => handleDragStart(e, item.mode)}
136+
>
137+
<ToggleGroupItem
138+
value={item.mode}
139+
aria-label={item.hotkey.description}
140+
disabled={item.disabled}
141+
className="hover:text-foreground-hover text-foreground-tertiary"
142+
>
143+
<item.icon />
144+
</ToggleGroupItem>
145+
</div>
146+
</TooltipTrigger>
147+
<TooltipContent>
148+
<HotkeyLabel hotkey={item.hotkey} />
149+
</TooltipContent>
150+
</Tooltip>
151+
))}
152+
</ToggleGroup>
153+
<Tooltip>
154+
<TooltipTrigger asChild>
155+
<button
156+
onClick={() => setTerminalHidden(!terminalHidden)}
157+
className="h-9 w-9 flex items-center justify-center hover:text-foreground-hover text-foreground-tertiary hover:bg-accent rounded-md"
158+
>
159+
<Icons.Terminal />
160+
</button>
161+
</TooltipTrigger>
162+
<TooltipContent>Toggle Terminal</TooltipContent>
163+
</Tooltip>
164+
</motion.div>
165+
) : (
166+
<motion.div
167+
layout
168+
className="flex items-center justify-between w-full mb-1"
169+
>
170+
<motion.span
171+
initial={{ opacity: 0, x: 10 }}
172+
animate={{ opacity: 1, x: 0 }}
173+
exit={{ opacity: 0, x: -10 }}
174+
transition={{ duration: 0.7 }}
175+
className="text-small text-foreground-secondary ml-2 select-none"
176+
>
177+
Terminal
178+
</motion.span>
179+
<div className="flex items-center gap-1">
180+
<motion.div layout>
181+
{/* <RunButton /> */}
182+
</motion.div>
183+
<Tooltip>
184+
<TooltipTrigger asChild>
185+
<button
186+
onClick={() => setTerminalHidden(!terminalHidden)}
187+
className="h-9 w-9 flex items-center justify-center hover:text-foreground-hover text-foreground-tertiary hover:bg-accent rounded-lg"
188+
>
189+
<Icons.ChevronDown />
190+
</button>
191+
</TooltipTrigger>
192+
<TooltipContent>Toggle Terminal</TooltipContent>
193+
</Tooltip>
194+
</div>
195+
</motion.div>
196+
)}
197+
{/* <Terminal hidden={terminalHidden} /> */}
198+
</motion.div>
199+
)}
200+
</AnimatePresence>
201+
);
202+
});

apps/web/client/src/app/project/[id]/_components/canvas/frame/right-click.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const RightClickMenu = observer(({ children }: RightClickMenuProps) => {
5555
{
5656
label: 'Add to AI Chat',
5757
action: () => {
58-
editorEngine.state.editorPanelTab = EditorTabValue.CHAT;
58+
editorEngine.state.rightPanelTab = EditorTabValue.CHAT;
5959
editorEngine.chat.focusChatInput();
6060
},
6161
icon: <Icons.MagicWand className="mr-2 h-4 w-4" />,
@@ -65,7 +65,7 @@ export const RightClickMenu = observer(({ children }: RightClickMenuProps) => {
6565
{
6666
label: 'New AI Chat',
6767
action: () => {
68-
editorEngine.state.editorPanelTab = EditorTabValue.CHAT;
68+
editorEngine.state.rightPanelTab = EditorTabValue.CHAT;
6969
editorEngine.chat.conversation.startNewConversation();
7070
editorEngine.chat.focusChatInput();
7171
},

apps/web/client/src/app/project/[id]/_components/canvas/hotkeys/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ export const HotkeysArea = ({ children }: { children: ReactNode }) => {
7272
});
7373

7474
// AI
75-
useHotkeys(Hotkey.ADD_AI_CHAT.command, () => (editorEngine.state.editorPanelTab = EditorTabValue.CHAT));
75+
useHotkeys(Hotkey.ADD_AI_CHAT.command, () => (editorEngine.state.rightPanelTab = EditorTabValue.CHAT));
7676
useHotkeys(Hotkey.NEW_AI_CHAT.command, () => {
77-
editorEngine.state.editorPanelTab = EditorTabValue.CHAT;
77+
editorEngine.state.rightPanelTab = EditorTabValue.CHAT;
7878
editorEngine.chat.conversation.startNewConversation();
7979
});
8080

apps/web/client/src/app/project/[id]/_components/canvas/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { useEditorEngine } from '@/components/store';
44
import { EditorAttributes } from '@onlook/models/constants';
55
import { EditorMode } from '@onlook/models/editor';
66
import { observer } from 'mobx-react-lite';
7-
import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
7+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8+
import { Frames } from './frames';
89
import { HotkeysArea } from './hotkeys';
910
import { Overlay } from './overlay';
1011
import { PanOverlay } from './overlay/pan';
@@ -18,7 +19,7 @@ const MAX_Y = 10000;
1819
const MIN_X = -5000;
1920
const MIN_Y = -5000;
2021

21-
export const Canvas = observer(({ children }: { children: ReactNode }) => {
22+
export const Canvas = observer(() => {
2223
const editorEngine = useEditorEngine();
2324
const containerRef = useRef<HTMLDivElement>(null);
2425
const [isPanning, setIsPanning] = useState(false);
@@ -164,7 +165,7 @@ export const Canvas = observer(({ children }: { children: ReactNode }) => {
164165
>
165166
<Overlay>
166167
<div id={EditorAttributes.CANVAS_CONTAINER_ID} style={transformStyle}>
167-
{children}
168+
<Frames />
168169
</div>
169170
</Overlay>
170171
<PanOverlay

apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/chat.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export const OverlayChat = observer(
8989

9090
const handleSubmit = async () => {
9191
const messageToSend = inputState.value;
92-
editorEngine.state.editorPanelTab = EditorTabValue.CHAT;
92+
editorEngine.state.rightPanelTab = EditorTabValue.CHAT;
9393
await editorEngine.chat.sendNewMessage(messageToSend);
9494
setInputState(DEFAULT_INPUT_STATE);
9595
};

0 commit comments

Comments
 (0)