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)}
+
+ )
))}