mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
- Remove editor from AppLayout so it owns the full viewport with no sidebar/padding - Add back-arrow in menu bar for navigation, restyle menu bar to match native app feel - Slim left toolbar from 48px to 36px with smaller icons and subtler dividers - Fix options bar overlapping canvas via shrink-0 and overflow-hidden - Fix opacity/blend controls clipping in right panel with min-w-0 on flex children - Add global range slider CSS for consistent thin-track + round-thumb styling - Make meme template routes public so img tags can load without Bearer auth - Show pipeline export buttons in default compact view, not just expanded hover - Queue individual AI feature installs instead of erroring when one is active
138 lines
3.8 KiB
TypeScript
138 lines
3.8 KiB
TypeScript
// apps/web/src/components/editor/editor-toolbar.tsx
|
|
//
|
|
// Toolbar layout, icons, and shortcuts follow the standard Photoshop
|
|
// convention so users coming from other editors feel at home.
|
|
import {
|
|
Blend,
|
|
BoxSelect,
|
|
Crop,
|
|
Droplet,
|
|
Droplets,
|
|
Eraser,
|
|
Fingerprint,
|
|
Flame,
|
|
Hand,
|
|
Hexagon,
|
|
Lasso,
|
|
Maximize2,
|
|
MousePointer2,
|
|
PaintBucket,
|
|
Paintbrush,
|
|
Pencil,
|
|
Pipette,
|
|
Stamp,
|
|
Sun,
|
|
Triangle,
|
|
Type,
|
|
Wand2,
|
|
ZoomIn,
|
|
} from "lucide-react";
|
|
import { useEditorStore } from "@/stores/editor-store";
|
|
import type { ToolType } from "@/types/editor";
|
|
import { IconButton } from "./common/icon-button";
|
|
|
|
interface ToolGroup {
|
|
tools: {
|
|
tool: ToolType;
|
|
icon: typeof MousePointer2;
|
|
label: string;
|
|
shortcut: string;
|
|
}[];
|
|
}
|
|
|
|
const TOOL_GROUPS: ToolGroup[] = [
|
|
{
|
|
tools: [
|
|
{ tool: "move", icon: MousePointer2, label: "Move", shortcut: "V" },
|
|
{ tool: "transform", icon: Maximize2, label: "Free Transform", shortcut: "Ctrl+T" },
|
|
],
|
|
},
|
|
{
|
|
tools: [
|
|
{ tool: "marquee-rect", icon: BoxSelect, label: "Marquee", shortcut: "M" },
|
|
{ tool: "lasso-free", icon: Lasso, label: "Lasso", shortcut: "L" },
|
|
{ tool: "magic-wand", icon: Wand2, label: "Magic Wand", shortcut: "W" },
|
|
],
|
|
},
|
|
{
|
|
tools: [
|
|
{ tool: "crop", icon: Crop, label: "Crop", shortcut: "C" },
|
|
{ tool: "eyedropper", icon: Pipette, label: "Eyedropper", shortcut: "I" },
|
|
],
|
|
},
|
|
{
|
|
tools: [
|
|
{ tool: "brush", icon: Paintbrush, label: "Brush", shortcut: "B" },
|
|
{ tool: "pencil", icon: Pencil, label: "Pencil", shortcut: "N" },
|
|
],
|
|
},
|
|
{
|
|
tools: [{ tool: "clone-stamp", icon: Stamp, label: "Clone Stamp", shortcut: "S" }],
|
|
},
|
|
{
|
|
tools: [{ tool: "eraser", icon: Eraser, label: "Eraser", shortcut: "E" }],
|
|
},
|
|
{
|
|
tools: [
|
|
{ tool: "fill", icon: PaintBucket, label: "Paint Bucket", shortcut: "G" },
|
|
{ tool: "gradient", icon: Blend, label: "Gradient", shortcut: "Shift+G" },
|
|
],
|
|
},
|
|
{
|
|
tools: [
|
|
{ tool: "blur-brush", icon: Droplet, label: "Blur", shortcut: "" },
|
|
{ tool: "sharpen-brush", icon: Triangle, label: "Sharpen", shortcut: "" },
|
|
{ tool: "smudge", icon: Fingerprint, label: "Smudge", shortcut: "" },
|
|
],
|
|
},
|
|
{
|
|
tools: [
|
|
{ tool: "dodge", icon: Sun, label: "Dodge", shortcut: "O" },
|
|
{ tool: "burn", icon: Flame, label: "Burn", shortcut: "Shift+O" },
|
|
{ tool: "sponge", icon: Droplets, label: "Sponge", shortcut: "Shift+O" },
|
|
],
|
|
},
|
|
{
|
|
tools: [{ tool: "shape-rect", icon: Hexagon, label: "Shape", shortcut: "U" }],
|
|
},
|
|
{
|
|
tools: [{ tool: "text", icon: Type, label: "Text", shortcut: "T" }],
|
|
},
|
|
{
|
|
tools: [
|
|
{ tool: "hand", icon: Hand, label: "Hand", shortcut: "H" },
|
|
{ tool: "zoom", icon: ZoomIn, label: "Zoom", shortcut: "Z" },
|
|
],
|
|
},
|
|
];
|
|
|
|
export function EditorToolbar() {
|
|
const activeTool = useEditorStore((s) => s.activeTool);
|
|
const setTool = useEditorStore((s) => s.setTool);
|
|
const sourceImageUrl = useEditorStore((s) => s.sourceImageUrl);
|
|
|
|
return (
|
|
<div className="flex flex-col items-center w-9 bg-card border-r border-border py-1.5 gap-0 overflow-y-auto">
|
|
{TOOL_GROUPS.map((group, gi) => (
|
|
<div key={group.tools[0].tool}>
|
|
{gi > 0 && <div className="w-4 h-px bg-border/50 mx-auto my-0.5" />}
|
|
{group.tools.map((t) => (
|
|
<IconButton
|
|
key={t.tool}
|
|
icon={t.icon}
|
|
label={t.label}
|
|
shortcut={t.shortcut}
|
|
active={activeTool === t.tool}
|
|
disabled={!sourceImageUrl && t.tool !== "hand" && t.tool !== "zoom"}
|
|
onClick={() => setTool(t.tool)}
|
|
data-testid={`tool-${t.tool}`}
|
|
data-tool={t.tool}
|
|
data-tool-active={String(activeTool === t.tool)}
|
|
/>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|