Skip to content

Commit

Permalink
Add hotkeys and tooltips (#193)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kitenite authored Aug 22, 2024
1 parent 297c127 commit f6c8b26
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 127 deletions.
Binary file modified app/bun.lockb
Binary file not shown.
29 changes: 29 additions & 0 deletions app/common/hotkeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { capitalizeFirstLetter } from './helpers';

export class Hotkeys {
static readonly UNDO = new Hotkeys('meta+z', 'Undo');
static readonly REDO = new Hotkeys('meta+shift+z', 'Redo');
static readonly SELECT = new Hotkeys('v', 'Select');
static readonly PAN = new Hotkeys('h', 'Pan');
static readonly INTERACT = new Hotkeys('i', 'Interact');
static readonly INSERT_DIV = new Hotkeys('r', 'Insert Div');
static readonly INSERT_TEXT = new Hotkeys('t', 'Insert Text');

// private to disallow creating other instances of this type
private constructor(
public readonly command: string,
public readonly description: string,
) {}

toString() {
return this.command;
}

get readableCommand() {
return this.command
.replace('meta', '⌘')
.split('+')
.map((value) => capitalizeFirstLetter(value))
.join(' ');
}
}
2 changes: 2 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@supabase/supabase-js": "^2.44.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand All @@ -65,6 +66,7 @@
"react-arborist": "^3.4.0",
"react-colorful": "^5.6.1",
"react-diff-viewer-continued": "^3.4.0",
"react-hotkeys-hook": "^4.5.0",
"react-tiny-popover": "^8.0.4",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7"
Expand Down
12 changes: 7 additions & 5 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { TooltipProvider } from '@/components/ui/tooltip';
import Announcement from './components/Announcement';
import AppBar from './components/AppBar';
import { ThemeProvider } from './components/theme-provider';
import { Toaster } from './components/ui/toaster';
import ProjectEditor from './routes/project';

function App() {
return (
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<AppBar />
<ProjectEditor />
<Announcement />
<Toaster />
<TooltipProvider>
<AppBar />
<ProjectEditor />
<Announcement />
<Toaster />
</TooltipProvider>
</ThemeProvider>
);
}
Expand Down
12 changes: 12 additions & 0 deletions app/src/components/ui/hotkeys-label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { Hotkeys } from '../../../common/hotkeys';
import { Kbd } from './kbd';

export function HotKeysLabel({ hotkey }: { hotkey: Hotkeys }) {
return (
<span className="space-x-2">
<span>{hotkey.description}</span>
<Kbd>{hotkey.readableCommand}</Kbd>
</span>
);
}
9 changes: 9 additions & 0 deletions app/src/components/ui/kbd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

export function Kbd({ children }: { children: React.ReactNode }) {
return (
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100">
{children}
</kbd>
);
}
28 changes: 28 additions & 0 deletions app/src/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import * as React from 'react';

import { cn } from '@/lib/utils';

const TooltipProvider = TooltipPrimitive.Provider;

const Tooltip = TooltipPrimitive.Root;

const TooltipTrigger = TooltipPrimitive.Trigger;

const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md bg-black px-3 py-1.5 text-xs text-text-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
3 changes: 3 additions & 0 deletions app/src/lib/editor/engine/action/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sendAnalytics } from '@/lib/utils';
import { HistoryManager } from '../history';
import { WebviewManager } from '../webview';
import {
Expand Down Expand Up @@ -27,6 +28,7 @@ export class ActionManager {
}

this.dispatch(action);
sendAnalytics('undo');
}

redo() {
Expand All @@ -36,6 +38,7 @@ export class ActionManager {
}

this.dispatch(action);
sendAnalytics('redo');
}

private dispatch(action: Action) {
Expand Down
92 changes: 35 additions & 57 deletions app/src/routes/project/Canvas/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
import { EditorMode } from '@/lib/models';
import { isMetaKey } from '@/lib/utils';
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import { ReactNode, useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useEditorEngine } from '..';
import PanOverlay from './PanOverlay';
import { Hotkeys } from '/common/hotkeys';

const Canvas = ({ children }: { children: ReactNode }) => {
const [position, setPosition] = useState({ x: 300, y: 50 });
const [scale, setScale] = useState(0.6);
const [isPanning, setIsPanning] = useState(false);
const ZOOM_SENSITIVITY = 0.006;
const PAN_SENSITIVITY = 0.52;
const DEFAULT_SCALE = 0.6;

const editorEngine = useEditorEngine();
const containerRef = useRef<HTMLDivElement>(null);
const zoomSensitivity = 0.006;
const panSensitivity = 0.52;
const [position, setPosition] = useState({ x: 300, y: 50 });
const [isPanning, setIsPanning] = useState(false);
const [scale, setScale] = useState(DEFAULT_SCALE);

// Zoom
useHotkeys('meta+0', () => setScale(DEFAULT_SCALE), { preventDefault: true });
useHotkeys('meta+equal', () => setScale(scale * 1.2), { preventDefault: true });
useHotkeys('meta+minus', () => setScale(scale * 0.8), { preventDefault: true });

// Modes
useHotkeys(Hotkeys.SELECT.command, () => (editorEngine.mode = EditorMode.DESIGN));
useHotkeys(Hotkeys.PAN.command, () => (editorEngine.mode = EditorMode.PAN));
useHotkeys(Hotkeys.INTERACT.command, () => (editorEngine.mode = EditorMode.INTERACT));
useHotkeys(Hotkeys.INSERT_DIV.command, () => (editorEngine.mode = EditorMode.INSERT_DIV));
// useHotkeys(Hotkeys.INSERT_TEXT.command, () => (editorEngine.mode = EditorMode.INSERT_TEXT));
useHotkeys('space', () => (editorEngine.mode = EditorMode.PAN), { keydown: true });
useHotkeys('space', () => (editorEngine.mode = EditorMode.DESIGN), { keyup: true });
useHotkeys('meta+alt', () =>
editorEngine.mode === EditorMode.INTERACT
? (editorEngine.mode = EditorMode.DESIGN)
: (editorEngine.mode = EditorMode.INTERACT),
);

// Actions
useHotkeys(Hotkeys.UNDO.command, () => editorEngine.action.undo());
useHotkeys(Hotkeys.REDO.command, () => editorEngine.action.redo());

const handleWheel = (event: WheelEvent) => {
if (event.ctrlKey || event.metaKey) {
Expand All @@ -27,7 +52,7 @@ const Canvas = ({ children }: { children: ReactNode }) => {
return;
}
event.preventDefault();
const zoomFactor = -event.deltaY * zoomSensitivity;
const zoomFactor = -event.deltaY * ZOOM_SENSITIVITY;
const rect = containerRef.current.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
Expand All @@ -44,8 +69,8 @@ const Canvas = ({ children }: { children: ReactNode }) => {
};

const handlePan = (event: WheelEvent) => {
const deltaX = (event.deltaX + (event.shiftKey ? event.deltaY : 0)) * panSensitivity;
const deltaY = (event.shiftKey ? 0 : event.deltaY) * panSensitivity;
const deltaX = (event.deltaX + (event.shiftKey ? event.deltaY : 0)) * PAN_SENSITIVITY;
const deltaY = (event.shiftKey ? 0 : event.deltaY) * PAN_SENSITIVITY;
setPosition((prevPosition) => ({
x: prevPosition.x - deltaX,
y: prevPosition.y - deltaY,
Expand Down Expand Up @@ -75,42 +100,6 @@ const Canvas = ({ children }: { children: ReactNode }) => {
}
}, [handleWheel]);

const handleZoomShortcut = (event: KeyboardEvent) => {
if (!isMetaKey(event)) {
return;
}
let shouldPreventDefault = true;
switch (event.key) {
case '0':
setScale(1);
break;
case '=':
setScale(scale * 1.2);
break;
case '-':
setScale(scale * 0.8);
break;
default:
shouldPreventDefault = false;
}

if (shouldPreventDefault) {
event.preventDefault();
}
};

const spaceBarDown = (e: KeyboardEvent) => {
if (e.key === ' ') {
editorEngine.mode = EditorMode.PAN;
}
};

const spaceBarUp = useCallback((e: KeyboardEvent) => {
if (e.key === ' ') {
editorEngine.mode = EditorMode.DESIGN;
}
}, []);

const middleMouseButtonDown = (e: MouseEvent) => {
if (e.button === 1) {
editorEngine.mode = EditorMode.PAN;
Expand All @@ -129,17 +118,6 @@ const Canvas = ({ children }: { children: ReactNode }) => {
}
};

useEffect(() => {
window.addEventListener('keydown', handleZoomShortcut);
window.addEventListener('keydown', spaceBarDown);
window.addEventListener('keyup', spaceBarUp);
return () => {
window.removeEventListener('keydown', handleZoomShortcut);
window.removeEventListener('keydown', spaceBarDown);
window.removeEventListener('keyup', spaceBarUp);
};
}, [handleZoomShortcut]);

useEffect(() => {
editorEngine.scale = scale;
}, [position, scale]);
Expand Down
70 changes: 51 additions & 19 deletions app/src/routes/project/Toolbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,45 @@
import { HotKeysLabel } from '@/components/ui/hotkeys-label';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { EditorMode } from '@/lib/models';
import { CursorArrowIcon, HandIcon, SquareIcon, TextIcon } from '@radix-ui/react-icons';
import clsx from 'clsx';
import { observer } from 'mobx-react-lite';
import { useEffect, useState } from 'react';
import { useEditorEngine } from '..';
import { Hotkeys } from '/common/hotkeys';

const TOOLBAR_ITEMS: {
mode: EditorMode;
icon: React.FC;
hotkey: Hotkeys;
disabled: boolean;
}[] = [
{
mode: EditorMode.DESIGN,
icon: CursorArrowIcon,
hotkey: Hotkeys.SELECT,
disabled: false,
},
{
mode: EditorMode.PAN,
icon: HandIcon,
hotkey: Hotkeys.PAN,
disabled: false,
},
{
mode: EditorMode.INSERT_DIV,
icon: SquareIcon,
hotkey: Hotkeys.INSERT_DIV,
disabled: false,
},
{
mode: EditorMode.INSERT_TEXT,
icon: TextIcon,
hotkey: Hotkeys.INSERT_TEXT,
disabled: true,
},
];

const Toolbar = observer(() => {
const editorEngine = useEditorEngine();
Expand All @@ -31,25 +66,22 @@ const Toolbar = observer(() => {
}
}}
>
<ToggleGroupItem value={EditorMode.DESIGN} aria-label={EditorMode.DESIGN + ' Mode'}>
<CursorArrowIcon />
</ToggleGroupItem>
<ToggleGroupItem value={EditorMode.PAN} aria-label={EditorMode.PAN + ' Mode'}>
<HandIcon />
</ToggleGroupItem>
<ToggleGroupItem
value={EditorMode.INSERT_DIV}
aria-label={EditorMode.INSERT_DIV + ' Mode'}
>
<SquareIcon />
</ToggleGroupItem>
<ToggleGroupItem
disabled
value={EditorMode.INSERT_TEXT}
aria-label={EditorMode.INSERT_TEXT + ' Mode'}
>
<TextIcon />
</ToggleGroupItem>
{TOOLBAR_ITEMS.map((item) => (
<Tooltip key={item.mode}>
<TooltipTrigger>
<ToggleGroupItem
value={item.mode}
aria-label={item.hotkey.description}
disabled={item.disabled}
>
<item.icon />
</ToggleGroupItem>
</TooltipTrigger>
<TooltipContent>
<HotKeysLabel hotkey={item.hotkey} />
</TooltipContent>
</Tooltip>
))}
</ToggleGroup>
</div>
);
Expand Down
Loading

0 comments on commit f6c8b26

Please sign in to comment.