diff --git a/apps/web/client/src/app/project/[id]/_components/canvas/hotkeys/index.tsx b/apps/web/client/src/app/project/[id]/_components/canvas/hotkeys/index.tsx index 84ce00cb15..09ff89fecc 100644 --- a/apps/web/client/src/app/project/[id]/_components/canvas/hotkeys/index.tsx +++ b/apps/web/client/src/app/project/[id]/_components/canvas/hotkeys/index.tsx @@ -7,6 +7,9 @@ import { useHotkeys } from 'react-hotkeys-hook'; export const HotkeysArea = ({ children }: { children: ReactNode }) => { const editorEngine = useEditorEngine(); + + // Check if we're in Code mode to disable certain hotkeys + const isCodeMode = editorEngine.state.editorMode === EditorMode.CODE; // Zoom useHotkeys( @@ -50,39 +53,61 @@ export const HotkeysArea = ({ children }: { children: ReactNode }) => { useHotkeys('alt', () => editorEngine.overlay.showMeasurement(), { keydown: true }); useHotkeys('alt', () => editorEngine.overlay.removeMeasurement(), { keyup: true }); - // Actions - useHotkeys(Hotkey.UNDO.command, () => editorEngine.action.undo(), { - preventDefault: true, + // Actions - disabled in Code mode + useHotkeys(Hotkey.UNDO.command, () => !isCodeMode && editorEngine.action.undo(), { + preventDefault: !isCodeMode, + enabled: !isCodeMode, }); - useHotkeys(Hotkey.REDO.command, () => editorEngine.action.redo(), { - preventDefault: true, + useHotkeys(Hotkey.REDO.command, () => !isCodeMode && editorEngine.action.redo(), { + preventDefault: !isCodeMode, + enabled: !isCodeMode, + }); + useHotkeys(Hotkey.ENTER.command, () => !isCodeMode && editorEngine.text.editSelectedElement(), { + preventDefault: !isCodeMode, + enabled: !isCodeMode }); - useHotkeys(Hotkey.ENTER.command, () => editorEngine.text.editSelectedElement(), { preventDefault: true }); useHotkeys([Hotkey.BACKSPACE.command, Hotkey.DELETE.command], () => { - if (editorEngine.elements.selected.length > 0) { - editorEngine.elements.delete(); + if (!isCodeMode) { + if (editorEngine.elements.selected.length > 0) { + editorEngine.elements.delete(); + } + else if (editorEngine.frames.selected.length > 0 && editorEngine.frames.canDelete()) { + editorEngine.frames.deleteSelected(); + } } - else if (editorEngine.frames.selected.length > 0 && editorEngine.frames.canDelete()) { - editorEngine.frames.deleteSelected(); - } - }, { preventDefault: true }); + }, { preventDefault: !isCodeMode, enabled: !isCodeMode }); - // Group - useHotkeys(Hotkey.GROUP.command, () => editorEngine.group.groupSelectedElements()); - useHotkeys(Hotkey.UNGROUP.command, () => editorEngine.group.ungroupSelectedElement()); + // Group - disabled in Code mode + useHotkeys(Hotkey.GROUP.command, () => !isCodeMode && editorEngine.group.groupSelectedElements(), { + enabled: !isCodeMode + }); + useHotkeys(Hotkey.UNGROUP.command, () => !isCodeMode && editorEngine.group.ungroupSelectedElement(), { + enabled: !isCodeMode + }); - // Copy - useHotkeys(Hotkey.COPY.command, () => editorEngine.copy.copy(), { preventDefault: true }); - useHotkeys(Hotkey.PASTE.command, () => editorEngine.copy.paste(), { preventDefault: true }); - useHotkeys(Hotkey.CUT.command, () => editorEngine.copy.cut(), { preventDefault: true }); + // Copy - disabled in Code mode + useHotkeys(Hotkey.COPY.command, () => !isCodeMode && editorEngine.copy.copy(), { + preventDefault: !isCodeMode, + enabled: !isCodeMode + }); + useHotkeys(Hotkey.PASTE.command, () => !isCodeMode && editorEngine.copy.paste(), { + preventDefault: !isCodeMode, + enabled: !isCodeMode + }); + useHotkeys(Hotkey.CUT.command, () => !isCodeMode && editorEngine.copy.cut(), { + preventDefault: !isCodeMode, + enabled: !isCodeMode + }); useHotkeys(Hotkey.DUPLICATE.command, () => { - if (editorEngine.elements.selected.length > 0) { - editorEngine.copy.duplicate(); - } - else if (editorEngine.frames.selected.length > 0 && editorEngine.frames.canDuplicate()) { - editorEngine.frames.duplicateSelected(); + if (!isCodeMode) { + if (editorEngine.elements.selected.length > 0) { + editorEngine.copy.duplicate(); + } + else if (editorEngine.frames.selected.length > 0 && editorEngine.frames.canDuplicate()) { + editorEngine.frames.duplicateSelected(); + } } - }, { preventDefault: true }); + }, { preventDefault: !isCodeMode, enabled: !isCodeMode }); // AI useHotkeys( diff --git a/apps/web/client/src/app/project/[id]/_components/code-panel/index.tsx b/apps/web/client/src/app/project/[id]/_components/code-panel/index.tsx new file mode 100644 index 0000000000..1075ce4af6 --- /dev/null +++ b/apps/web/client/src/app/project/[id]/_components/code-panel/index.tsx @@ -0,0 +1,27 @@ +'use client'; + +import { ResizablePanel } from '@onlook/ui/resizable'; +import { cn } from '@onlook/ui/utils'; +import { observer } from 'mobx-react-lite'; +import { CodeTab } from '../right-panel/code-tab'; + +export const CodePanel = observer(() => { + return ( +
+ +
+ +
+
+
+ ); +}); \ No newline at end of file diff --git a/apps/web/client/src/app/project/[id]/_components/main.tsx b/apps/web/client/src/app/project/[id]/_components/main.tsx index d0acfed0c5..c36d8431a3 100644 --- a/apps/web/client/src/app/project/[id]/_components/main.tsx +++ b/apps/web/client/src/app/project/[id]/_components/main.tsx @@ -1,8 +1,10 @@ 'use client'; +import { useEditorEngine } from '@/components/store/editor'; import { SubscriptionModal } from '@/components/ui/pricing-modal'; import { SettingsModalWithProjects } from '@/components/ui/settings-modal/with-project'; import { EditorAttributes } from '@onlook/constants'; +import { EditorMode } from '@onlook/models'; import { Button } from '@onlook/ui/button'; import { Icons } from '@onlook/ui/icons'; import { TooltipProvider } from '@onlook/ui/tooltip'; @@ -13,6 +15,7 @@ import { usePanelMeasurements } from '../_hooks/use-panel-measure'; import { useStartProject } from '../_hooks/use-start-project'; import { BottomBar } from './bottom-bar'; import { Canvas } from './canvas'; +import { CodePanel } from './code-panel'; import { EditorBar } from './editor-bar'; import { LeftPanel } from './left-panel'; import { RightPanel } from './right-panel'; @@ -20,11 +23,13 @@ import { TopBar } from './top-bar'; export const Main = observer(() => { const router = useRouter(); + const editorEngine = useEditorEngine(); const { isProjectReady, error } = useStartProject(); const leftPanelRef = useRef(null); const rightPanelRef = useRef(null); + const codePanelRef = useRef(null); const { toolbarLeft, toolbarRight, editorBarAvailableWidth } = usePanelMeasurements( - leftPanelRef, + editorEngine.state.editorMode === EditorMode.CODE ? codePanelRef : leftPanelRef, rightPanelRef, ); @@ -84,31 +89,45 @@ export const Main = observer(() => { - {/* Left Panel */} -
- -
- {/* EditorBar anchored between panels */} -
-
- + {/* Left Panel - only in Design mode */} + {editorEngine.state.editorMode === EditorMode.DESIGN && ( +
+
-
+ )} + + {/* Code Panel - only in Code mode */} + {editorEngine.state.editorMode === EditorMode.CODE && ( +
+ +
+ )} + {/* EditorBar anchored between panels - only in Design mode */} + {editorEngine.state.editorMode === EditorMode.DESIGN && ( +
+
+ +
+
+ )} {/* Right Panel */}
{ menuItems = [WINDOW_ITEMS]; } else { const updatedToolItems = [ + { + label: 'Open in Code', + action: () => { + editorEngine.state.editorMode = EditorMode.CODE; + viewSource(root); + }, + icon: , + disabled: !root, + }, instance !== null && { label: 'View instance code', action: () => viewSource(instance), diff --git a/apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx b/apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx index 066061bd24..9b60473e5e 100644 --- a/apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx +++ b/apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx @@ -28,7 +28,10 @@ export const RightPanel = observer(() => { const [isChatHistoryOpen, setIsChatHistoryOpen] = useState(false); const [inputValue, setInputValue] = useState(''); - const selectedTab = editorEngine.state.rightPanelTab; + // Force chat tab when in code mode + const selectedTab = editorEngine.state.editorMode === EditorMode.CODE + ? EditorTabValue.CHAT + : editorEngine.state.rightPanelTab; const editPanelWidth = EDIT_PANEL_WIDTHS[selectedTab]; return ( @@ -61,13 +64,15 @@ export const RightPanel = observer(() => { - - - Code - + {editorEngine.state.editorMode !== EditorMode.CODE && ( + + + Code + + )}
{selectedTab === EditorTabValue.CHAT && } {selectedTab === EditorTabValue.DEV && } diff --git a/apps/web/client/src/app/project/[id]/_components/top-bar/mode-toggle.tsx b/apps/web/client/src/app/project/[id]/_components/top-bar/mode-toggle.tsx index 2e443a798a..23180ae8aa 100644 --- a/apps/web/client/src/app/project/[id]/_components/top-bar/mode-toggle.tsx +++ b/apps/web/client/src/app/project/[id]/_components/top-bar/mode-toggle.tsx @@ -12,12 +12,15 @@ import { useTranslations } from 'next-intl'; const MODE_TOGGLE_ITEMS: { mode: EditorMode; - hotkey: Hotkey; + hotkey?: Hotkey; }[] = [ { mode: EditorMode.DESIGN, hotkey: Hotkey.SELECT, }, + { + mode: EditorMode.CODE, + }, { mode: EditorMode.PREVIEW, hotkey: Hotkey.PREVIEW, @@ -27,12 +30,14 @@ const MODE_TOGGLE_ITEMS: { export const ModeToggle = observer(() => { const t = useTranslations(); const editorEngine = useEditorEngine(); - const mode: EditorMode.DESIGN | EditorMode.PREVIEW = getNormalizedMode( + const mode: EditorMode.DESIGN | EditorMode.CODE | EditorMode.PREVIEW = getNormalizedMode( editorEngine.state.editorMode, ); function getNormalizedMode(unnormalizedMode: EditorMode) { - return unnormalizedMode === EditorMode.PREVIEW ? EditorMode.PREVIEW : EditorMode.DESIGN; + if (unnormalizedMode === EditorMode.PREVIEW) return EditorMode.PREVIEW; + if (unnormalizedMode === EditorMode.CODE) return EditorMode.CODE; + return EditorMode.DESIGN; } return ( @@ -48,33 +53,49 @@ export const ModeToggle = observer(() => { }} > {MODE_TOGGLE_ITEMS.map((item) => ( - - - - {t(transKeys.editor.modes[item.mode.toLowerCase() as keyof typeof transKeys.editor.modes].name)} - - - - - - + item.hotkey ? ( + + + + {item.mode === EditorMode.CODE ? 'Code' : t(transKeys.editor.modes[item.mode.toLowerCase() as keyof typeof transKeys.editor.modes].name)} + + + + + + + ) : ( + + {item.mode === EditorMode.CODE ? 'Code' : t(transKeys.editor.modes[item.mode.toLowerCase() as keyof typeof transKeys.editor.modes].name)} + + ) ))}