Skip to content

Commit 98ccb2e

Browse files
committed
feat(ui): add global bookmark/example shortcuts
1 parent a93596f commit 98ccb2e

1 file changed

Lines changed: 46 additions & 3 deletions

File tree

frontend/src/app/[locale]/page.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
"use client";
22

33
import { useTranslations } from "next-intl";
4-
import { useState, useEffect, useRef, useMemo, type KeyboardEvent } from "react";
4+
import {
5+
useState,
6+
useEffect,
7+
useRef,
8+
useMemo,
9+
useCallback,
10+
type KeyboardEvent as ReactKeyboardEvent,
11+
} from "react";
512
import { useParams } from "next/navigation";
613
import { useSimulationStore, WorldData } from "@/stores/simulation";
714
import { CommandPalette } from "@/components/CommandPalette";
@@ -54,6 +61,7 @@ export default function HomePage() {
5461
const fetchedCount = useRef(0);
5562
const { createWorld, autoWorlds, fetchAutoWorlds, worldTags, tagFilter, setTagFilter } = useSimulationStore();
5663
const createHintText = locale === "ko" ? "팁: 빠르게 생성하려면 Ctrl/Cmd + Enter" : "Tip: Press Ctrl/Cmd + Enter to create quickly.";
64+
const shortcutHintText = locale === "ko" ? "단축키: Ctrl/Cmd + B(북마크 열기), Ctrl/Cmd + R(예시 회전)" : "Shortcuts: Ctrl/Cmd + B (open bookmarks), Ctrl/Cmd + R (rotate example).";
5765
const { setDrawerOpen } = useBookmarkStore();
5866
const { fetchNode } = useTaxonomyStore();
5967
const [taxonomyWorldFilter, setTaxonomyWorldFilter] = useState<string | null>(null);
@@ -166,6 +174,10 @@ export default function HomePage() {
166174
return () => clearInterval(interval);
167175
}, [fetchAutoWorlds]);
168176

177+
const handleExampleRotate = useCallback(() => {
178+
setSeedPrompt(examples[exampleIndex % examples.length]);
179+
}, [exampleIndex, examples]);
180+
169181
const handleCreate = async () => {
170182
if (!seedPrompt.trim()) return;
171183
setCreating(true);
@@ -191,17 +203,45 @@ export default function HomePage() {
191203
seedPromptInputRef.current?.focus();
192204
};
193205

194-
const handleSeedKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
206+
const handleSeedKeyDown = (event: ReactKeyboardEvent<HTMLTextAreaElement>) => {
195207
if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
196208
event.preventDefault();
197209
handleCreate();
198210
}
199211
};
200212

201213
const handleExampleClick = () => {
202-
setSeedPrompt(examples[exampleIndex % examples.length]);
214+
handleExampleRotate();
203215
};
204216

217+
const handleGlobalKeyDown = useCallback((event: globalThis.KeyboardEvent) => {
218+
const activeTag = (event.target as HTMLElement | null)?.tagName;
219+
const isEditing = activeTag ? ["INPUT", "TEXTAREA", "SELECT"].includes(activeTag) : false;
220+
221+
if (isEditing) return;
222+
223+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "b") {
224+
event.preventDefault();
225+
setDrawerOpen(true);
226+
setToast("Bookmarks drawer opened");
227+
setTimeout(() => setToast(null), 1600);
228+
}
229+
230+
if (event.key.toLowerCase() === "r" && (event.metaKey || event.ctrlKey)) {
231+
event.preventDefault();
232+
handleExampleRotate();
233+
setToast("Example rotated");
234+
setTimeout(() => setToast(null), 1000);
235+
}
236+
}, [handleExampleRotate, setDrawerOpen, setToast]);
237+
238+
useEffect(() => {
239+
if (typeof document === "undefined") return;
240+
241+
document.addEventListener("keydown", handleGlobalKeyDown);
242+
return () => document.removeEventListener("keydown", handleGlobalKeyDown);
243+
}, [handleGlobalKeyDown]);
244+
205245
const isSeedNearLimit = seedPrompt.length >= Math.floor(MAX_SEED_PROMPT_LENGTH * 0.9);
206246

207247
const seedCounterLabel =
@@ -416,6 +456,9 @@ export default function HomePage() {
416456
<p className="text-[11px] text-hud-label">
417457
{createHintText}
418458
</p>
459+
<p className="text-[11px] text-hud-label">
460+
{shortcutHintText}
461+
</p>
419462
<div className="flex flex-wrap gap-2">
420463
<button
421464
onClick={handleCreate}

0 commit comments

Comments
 (0)