feat: implement chroma key operation and related keybinds, update workspace panel management
This commit is contained in:
@@ -13,6 +13,14 @@ describe("tool commands", () => {
|
||||
expect(next.editor.tools).toEqual({ activeTool: "brush", interactionMode: { type: "tool", tool: "brush" }, brush: defaultBrush, generate: defaultGenerate, chromaKey: defaultChromaKey, magicWand: defaultMagicWand });
|
||||
});
|
||||
|
||||
test("selecting a persistent tool closes an open operation", () => {
|
||||
const state = createInitialAppState("Test");
|
||||
state.editor.workspace = { panel: "chromaKey" };
|
||||
const next = toolSetActiveCommand.execute({ state }, { tool: "brush" });
|
||||
expect(next.editor.tools.activeTool).toBe("brush");
|
||||
expect(next.editor.workspace.panel).toBe("none");
|
||||
});
|
||||
|
||||
test("sets brush settings", () => {
|
||||
const next = toolSetBrushSettingsCommand.execute({ state: createInitialAppState("Test") }, { color: "#ff0000", size: 24, hardness: 50 });
|
||||
|
||||
|
||||
@@ -44,8 +44,6 @@ export const toolSetActiveCommand: Command<ToolSetActivePayload> = {
|
||||
id: commandIds.toolSetActive,
|
||||
name: "Set active tool",
|
||||
execute({ state }, payload) {
|
||||
const previousNonGenerateTool = payload.tool === "generate" ? state.editor.workspace.previousNonGenerateTool : payload.tool;
|
||||
const panel = payload.tool === "generate" ? "generate" : state.editor.workspace.panel === "generate" ? "none" : state.editor.workspace.panel;
|
||||
return {
|
||||
...state,
|
||||
editor: {
|
||||
@@ -57,7 +55,9 @@ export const toolSetActiveCommand: Command<ToolSetActivePayload> = {
|
||||
},
|
||||
brushPreview: undefined,
|
||||
brushStrokePreview: undefined,
|
||||
workspace: { panel, previousNonGenerateTool },
|
||||
workspace: state.editor.workspace.panel === "generate" || state.editor.workspace.panel === "chromaKey"
|
||||
? { panel: "none" }
|
||||
: state.editor.workspace,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
13
commands/workspace.test.ts
Normal file
13
commands/workspace.test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { createInitialAppState } from "@editor/initial-state";
|
||||
import { workspaceSetPanelCommand } from "./workspace";
|
||||
|
||||
describe("workspace commands", () => {
|
||||
test.each(["generate", "chromaKey"] as const)("opens the %s operation without changing the persistent tool", (panel) => {
|
||||
const state = createInitialAppState("Test");
|
||||
const next = workspaceSetPanelCommand.execute({ state }, { panel });
|
||||
expect(next.editor.workspace.panel).toBe(panel);
|
||||
expect(next.editor.tools.activeTool).toBe("select");
|
||||
expect(next.editor.tools.interactionMode).toEqual({ type: "tool", tool: "select" });
|
||||
});
|
||||
});
|
||||
@@ -10,23 +10,17 @@ export const workspaceSetPanelCommand: Command<WorkspaceSetPanelPayload> = {
|
||||
history: { mode: "ignore" },
|
||||
execute({ state }, payload) {
|
||||
if (!workspacePanels.has(payload.panel)) return state;
|
||||
const currentTool = state.editor.tools.activeTool;
|
||||
const previousNonGenerateTool = currentTool === "generate" ? state.editor.workspace.previousNonGenerateTool : currentTool;
|
||||
const nextTool = payload.panel === "generate" ? "generate" : currentTool === "generate" ? previousNonGenerateTool : currentTool;
|
||||
if (state.editor.workspace.panel === payload.panel && currentTool === nextTool) return state;
|
||||
if (state.editor.workspace.panel === payload.panel) return state;
|
||||
return {
|
||||
...state,
|
||||
editor: {
|
||||
...state.editor,
|
||||
workspace: { panel: payload.panel, previousNonGenerateTool },
|
||||
tools: nextTool === currentTool ? state.editor.tools : { ...state.editor.tools, activeTool: nextTool, interactionMode: { type: "tool", tool: nextTool } },
|
||||
brushPreview: nextTool === currentTool ? state.editor.brushPreview : undefined,
|
||||
brushStrokePreview: nextTool === currentTool ? state.editor.brushStrokePreview : undefined,
|
||||
workspace: { panel: payload.panel },
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const workspacePanels = new Set<WorkspacePanel>(["none", "generate", "layers"]);
|
||||
const workspacePanels = new Set<WorkspacePanel>(["none", "generate", "chromaKey", "layers"]);
|
||||
|
||||
export const workspaceCommands = [workspaceSetPanelCommand] satisfies Command<unknown>[];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type { AppState, EditorState, SelectionState, ViewportState } from "./state";
|
||||
export type { BrushSettings, ChromaKeySettings, MagicWandSettings, InteractionMode, ToolId, ToolState } from "./tools";
|
||||
export type { BrushSettings, ChromaKeySettings, MagicWandSettings, InteractionMode, OperationId, ToolId, ToolState } from "./tools";
|
||||
export { initialToolState } from "./tools";
|
||||
export { createInitialAppState, initialEditorState } from "./initial-state";
|
||||
export type { AppStore, StateListener } from "./store";
|
||||
|
||||
@@ -26,7 +26,6 @@ export const initialEditorState: EditorState = {
|
||||
},
|
||||
workspace: {
|
||||
panel: "none",
|
||||
previousNonGenerateTool: "select",
|
||||
},
|
||||
transformSession: undefined,
|
||||
maskEdit: undefined,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ImageDocument } from "@core/document";
|
||||
import type { Angle, Rect, Size, Transform, Vec2D } from "@core/geometry";
|
||||
import type { ArtboardId, AssetId, GenerationCandidateId, GenerationJobId, LayerId } from "@core/id";
|
||||
import type { GenerateArchitecture, GenerateMode, GenerateSettings, ToolId, ToolState } from "./tools";
|
||||
import type { GenerateArchitecture, GenerateMode, GenerateSettings, ToolState } from "./tools";
|
||||
import type { TransformSession } from "./transform";
|
||||
|
||||
export type ViewportState = {
|
||||
@@ -127,11 +127,10 @@ export type CommandPaletteState = {
|
||||
selectedIndex: number;
|
||||
};
|
||||
|
||||
export type WorkspacePanel = "none" | "generate" | "layers";
|
||||
export type WorkspacePanel = "none" | "generate" | "chromaKey" | "layers";
|
||||
|
||||
export type WorkspaceState = {
|
||||
panel: WorkspacePanel;
|
||||
previousNonGenerateTool: ToolId;
|
||||
};
|
||||
|
||||
export type EditorState = {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
export const availableToolIds = ["select", "generate", "brush", "eraser", "chromaKey", "magicWand", "pan"] as const;
|
||||
export const availableToolIds = ["select", "brush", "eraser", "magicWand", "pan"] as const;
|
||||
|
||||
export const availableOperationIds = ["generate", "chromaKey"] as const;
|
||||
|
||||
export type ToolId = (typeof availableToolIds)[number];
|
||||
export type OperationId = (typeof availableOperationIds)[number];
|
||||
|
||||
export type InteractionMode =
|
||||
| { type: "tool"; tool: ToolId }
|
||||
|
||||
@@ -17,7 +17,7 @@ describe("command palette input", () => {
|
||||
expect(dispatched).toEqual([{ commandId: commandIds.commandPaletteOpen, payload: undefined }]);
|
||||
});
|
||||
|
||||
test("ignores unmodified K so the chroma key tool keeps its shortcut", () => {
|
||||
test("ignores unmodified K so the chroma key operation keeps its shortcut", () => {
|
||||
const consumed = handleCommandPaletteKey({
|
||||
event: { key: "k", code: "KeyK", altKey: false, ctrlKey: false, metaKey: false, shiftKey: false },
|
||||
dispatch: () => undefined as never,
|
||||
|
||||
@@ -8,6 +8,7 @@ export { handleKeybind, keybindFromEvent } from "./keyboard";
|
||||
export { handleCommandPaletteKey } from "./command-palette";
|
||||
export { handleHistoryKey } from "./history";
|
||||
export { handleToolKey } from "./tool-keybinds";
|
||||
export { handleOperationKey } from "./operation-keybinds";
|
||||
export { findGroup, findLayerInfoInDocument, handleDeleteSelectionKey, resolveLayerDrop } from "./layers-panel";
|
||||
export type { LayerDropTarget, LayerInfo } from "./layers-panel";
|
||||
export { handleArtboardSelection } from "./selection";
|
||||
|
||||
22
input/operation-keybinds.test.ts
Normal file
22
input/operation-keybinds.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { commandIds } from "@commands/ids";
|
||||
import type { Dispatch } from "@commands/dispatcher";
|
||||
import { handleOperationKey } from "./operation-keybinds";
|
||||
|
||||
describe("operation keybinds", () => {
|
||||
test.each([
|
||||
["g", "generate"],
|
||||
["k", "chromaKey"],
|
||||
] as const)("opens the %s operation without selecting a tool", (key, panel) => {
|
||||
const dispatched: unknown[] = [];
|
||||
const consumed = handleOperationKey({
|
||||
event: { key, code: `Key${key.toUpperCase()}`, altKey: false, ctrlKey: false, metaKey: false, shiftKey: false },
|
||||
dispatch: ((commandId: unknown, payload: unknown) => {
|
||||
dispatched.push({ commandId, payload });
|
||||
return undefined;
|
||||
}) as unknown as Dispatch,
|
||||
});
|
||||
expect(consumed).toBe(true);
|
||||
expect(dispatched).toEqual([{ commandId: commandIds.workspaceSetPanel, payload: { panel } }]);
|
||||
});
|
||||
});
|
||||
16
input/operation-keybinds.ts
Normal file
16
input/operation-keybinds.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { commandIds } from "@commands/ids";
|
||||
import type { Dispatch } from "@commands/dispatcher";
|
||||
import type { KeybindEvent } from "./keyboard";
|
||||
|
||||
const operationKeybinds = {
|
||||
g: "generate",
|
||||
k: "chromaKey",
|
||||
} as const;
|
||||
|
||||
export function handleOperationKey(options: { event: KeybindEvent; dispatch: Dispatch }): boolean {
|
||||
if (options.event.altKey || options.event.ctrlKey || options.event.metaKey) return false;
|
||||
const panel = operationKeybinds[options.event.key.toLowerCase() as keyof typeof operationKeybinds];
|
||||
if (!panel) return false;
|
||||
options.dispatch(commandIds.workspaceSetPanel, { panel });
|
||||
return true;
|
||||
}
|
||||
@@ -3,10 +3,8 @@ import type { Dispatch } from "@commands/dispatcher";
|
||||
import type { KeybindEvent } from "./keyboard";
|
||||
|
||||
const toolKeybinds = {
|
||||
g: "generate",
|
||||
b: "brush",
|
||||
e: "eraser",
|
||||
k: "chromaKey",
|
||||
w: "magicWand",
|
||||
p: "pan",
|
||||
s: "select",
|
||||
|
||||
@@ -16,7 +16,7 @@ import type { PointerInputEvent } from "./pointer";
|
||||
|
||||
type TransformHandle = "body" | "nw" | "n" | "ne" | "e" | "se" | "s" | "sw" | "w";
|
||||
|
||||
type InputToolId = "select" | "generate" | "brush" | "eraser" | "chromaKey" | "magicWand" | "pan";
|
||||
type InputToolId = "select" | "brush" | "eraser" | "magicWand" | "pan";
|
||||
|
||||
type InputInteractionMode =
|
||||
| { type: "tool"; tool: InputToolId }
|
||||
|
||||
@@ -28,6 +28,7 @@ export async function applyChromaKeyMask(target: NonNullable<ReturnType<typeof r
|
||||
dispatch(commandIds.toolSetBrushStrokePreview, undefined);
|
||||
if (target.maskAsset && target.maskLayer && target.maskLayer.type !== "group") {
|
||||
dispatch(commandIds.documentApplyLayerMaskOperation, { maskLayerId: target.maskLayer.id, source, mimeType: "image/png", operation: { type: "chromaKey" } });
|
||||
dispatch(commandIds.workspaceSetPanel, { panel: "none" });
|
||||
return;
|
||||
}
|
||||
const assetId = crypto.randomUUID();
|
||||
@@ -40,7 +41,7 @@ export async function applyChromaKeyMask(target: NonNullable<ReturnType<typeof r
|
||||
maskLayer: { id: maskLayerId, type: "raster", name: `${target.layer.name} Chroma Mask`, visible: true, locked: false, opacity: 1, assetId, transform: { position: { x: target.bounds.x, y: target.bounds.y }, scale: { x: target.bounds.w / width, y: target.bounds.h / height }, rotation: target.layer.transform.rotation } },
|
||||
});
|
||||
dispatch(commandIds.toolExitMaskEdit, undefined);
|
||||
dispatch(commandIds.toolSetActive, { tool: "chromaKey" });
|
||||
dispatch(commandIds.workspaceSetPanel, { panel: "none" });
|
||||
}
|
||||
|
||||
function findLayer(layers: readonly Layer[], layerId: string): Layer | undefined {
|
||||
|
||||
13
view/App.tsx
13
view/App.tsx
@@ -13,7 +13,7 @@ import { ShortcutsDisplay } from "./ShortcutsDisplay";
|
||||
import { ToolOverlay } from "./ToolOverlay";
|
||||
import { resolveTransformTargetBounds, selectedTransformTarget } from "@editor/transform-targets";
|
||||
import type { AppState } from "@editor/state";
|
||||
import { handleCommandPaletteKey, handleDeleteSelectionKey, handleHistoryKey, handleToolKey, keybindEventFromKeyboardEvent } from "@input/index";
|
||||
import { handleCommandPaletteKey, handleDeleteSelectionKey, handleHistoryKey, handleOperationKey, handleToolKey, keybindEventFromKeyboardEvent } from "@input/index";
|
||||
import { shallowEqual, useAppState } from "./useAppState";
|
||||
import { downloadArtboardPng } from "@operations/export/downloadArtboard";
|
||||
import { useImageImport } from "./useImageImport";
|
||||
@@ -34,6 +34,7 @@ export function App({ app }: AppProps) {
|
||||
const transformTarget = transformSession?.target ?? selectedTransformTarget(document, selection);
|
||||
const activeArtboard = document.artboards.find((artboard) => artboard.id === selection.artboardId) ?? document.artboards[0];
|
||||
const generateOpen = workspace.panel === "generate";
|
||||
const chromaKeyOpen = workspace.panel === "chromaKey";
|
||||
const layersOpen = workspace.panel === "layers";
|
||||
|
||||
useEffect(() => {
|
||||
@@ -105,6 +106,12 @@ export function App({ app }: AppProps) {
|
||||
|
||||
const key = event.key.toLowerCase();
|
||||
|
||||
const operationConsumed = handleOperationKey({ event: keybindEvent, dispatch: app.store.dispatch });
|
||||
if (operationConsumed) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const toolConsumed = handleToolKey({ event: keybindEvent, dispatch: app.store.dispatch });
|
||||
if (toolConsumed) {
|
||||
event.preventDefault();
|
||||
@@ -179,6 +186,7 @@ export function App({ app }: AppProps) {
|
||||
<ToolOverlay
|
||||
activeTool={tools.activeTool}
|
||||
interactionMode={tools.interactionMode}
|
||||
panel={workspace.panel}
|
||||
dispatch={app.store.dispatch}
|
||||
/>
|
||||
</div>
|
||||
@@ -200,9 +208,10 @@ export function App({ app }: AppProps) {
|
||||
document={document}
|
||||
selection={selection}
|
||||
viewport={viewport}
|
||||
visible={tools.activeTool === "generate" || tools.activeTool === "brush" || tools.activeTool === "eraser" || tools.activeTool === "chromaKey" || tools.activeTool === "magicWand" || Boolean(transformBounds) || viewportActivityIsland.visible}
|
||||
visible={generateOpen || chromaKeyOpen || tools.activeTool === "brush" || tools.activeTool === "eraser" || tools.activeTool === "magicWand" || Boolean(transformBounds) || viewportActivityIsland.visible}
|
||||
action={viewportActivityIsland.action}
|
||||
activeTool={tools.activeTool}
|
||||
operation={generateOpen ? "generate" : chromaKeyOpen ? "chromaKey" : undefined}
|
||||
brushSettings={tools.brush}
|
||||
generateSettings={tools.generate}
|
||||
generation={generation}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AppStore } from "@editor/store";
|
||||
import type { ImageDocument } from "@core/document";
|
||||
import type { GenerationState, MaskViewMode, SelectionState, ViewportState } from "@editor/state";
|
||||
import type { BrushSettings, ChromaKeySettings, GenerateSettings, MagicWandSettings, ToolId } from "@editor/tools";
|
||||
import type { BrushSettings, ChromaKeySettings, GenerateSettings, MagicWandSettings, OperationId, ToolId } from "@editor/tools";
|
||||
import { BrushControls } from "./bottom-controls/BrushControls";
|
||||
import { ChromaKeyControls } from "./bottom-controls/ChromaKeyControls";
|
||||
import { MagicWandControls } from "./bottom-controls/MagicWandControls";
|
||||
@@ -21,6 +21,7 @@ export type BottomControlsIslandProps = {
|
||||
visible: boolean;
|
||||
action: BottomControlsAction;
|
||||
activeTool: ToolId;
|
||||
operation?: OperationId;
|
||||
brushSettings: BrushSettings;
|
||||
generateSettings: GenerateSettings;
|
||||
generation: GenerationState;
|
||||
@@ -35,7 +36,7 @@ export type BottomControlsIslandProps = {
|
||||
generationWorkflow: GenerationWorkflow;
|
||||
};
|
||||
|
||||
export function BottomControlsIsland({ document, selection, viewport, visible, action, activeTool, brushSettings, generateSettings, generation, chromaKeySettings, magicWandSettings, editingMask = false, maskViewMode = "composite", transformBounds, transformTarget, brushHint, dispatch, generationWorkflow }: BottomControlsIslandProps) {
|
||||
export function BottomControlsIsland({ document, selection, viewport, visible, action, activeTool, operation, brushSettings, generateSettings, generation, chromaKeySettings, magicWandSettings, editingMask = false, maskViewMode = "composite", transformBounds, transformTarget, brushHint, dispatch, generationWorkflow }: BottomControlsIslandProps) {
|
||||
const zoomPercent = Math.round(viewport.zoom * 100);
|
||||
const x = Math.round(viewport.center.x);
|
||||
const y = Math.round(viewport.center.y);
|
||||
@@ -47,13 +48,13 @@ export function BottomControlsIsland({ document, selection, viewport, visible, a
|
||||
visible ? "pointer-events-auto translate-y-0 opacity-100" : "pointer-events-none translate-y-3 opacity-0"
|
||||
}`}
|
||||
>
|
||||
{activeTool === "generate" ? (
|
||||
{operation === "generate" ? (
|
||||
<GenerateActionControls settings={generateSettings} generation={generation} dispatch={dispatch} workflow={generationWorkflow} />
|
||||
) : (activeTool === "brush" || activeTool === "eraser") && brushHint ? (
|
||||
<BrushHint tool={activeTool} hint={brushHint} />
|
||||
) : activeTool === "brush" || activeTool === "eraser" ? (
|
||||
<BrushControls tool={activeTool} settings={brushSettings} editingMask={editingMask} maskViewMode={maskViewMode} dispatch={dispatch} />
|
||||
) : activeTool === "chromaKey" ? (
|
||||
) : operation === "chromaKey" ? (
|
||||
<ChromaKeyControls document={document} selection={selection} settings={chromaKeySettings} dispatch={dispatch} />
|
||||
) : activeTool === "magicWand" ? (
|
||||
<MagicWandControls settings={magicWandSettings} dispatch={dispatch} />
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { Cursor, Eraser, Hand, PaintBrush, DropHalf, MagicWand, Sparkle } from "@phosphor-icons/react";
|
||||
import { commandIds } from "@commands/ids";
|
||||
import type { AppStore } from "@editor/store";
|
||||
import type { InteractionMode, ToolId } from "@editor/tools";
|
||||
import { availableToolIds } from "@editor/tools";
|
||||
import type { InteractionMode, OperationId, ToolId } from "@editor/tools";
|
||||
import { availableOperationIds, availableToolIds } from "@editor/tools";
|
||||
import type { WorkspacePanel } from "@editor/state";
|
||||
import { labelForTool } from "./toolLabels";
|
||||
|
||||
export type ToolOverlayProps = {
|
||||
activeTool: ToolId;
|
||||
interactionMode: InteractionMode;
|
||||
panel: WorkspacePanel;
|
||||
dispatch: AppStore["dispatch"];
|
||||
};
|
||||
|
||||
export function ToolOverlay({ activeTool, interactionMode, dispatch }: ToolOverlayProps) {
|
||||
export function ToolOverlay({ activeTool, interactionMode, panel, dispatch }: ToolOverlayProps) {
|
||||
return (
|
||||
<nav
|
||||
aria-label="Tools"
|
||||
@@ -35,20 +37,34 @@ export function ToolOverlay({ activeTool, interactionMode, dispatch }: ToolOverl
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<div className="h-px w-8 bg-white/15" aria-hidden="true" />
|
||||
{availableOperationIds.map((operation) => {
|
||||
const active = panel === operation;
|
||||
const Icon = iconForOperation(operation);
|
||||
return (
|
||||
<button
|
||||
key={operation}
|
||||
type="button"
|
||||
aria-label={labelForOperation(operation)}
|
||||
aria-pressed={active}
|
||||
title={labelForOperation(operation)}
|
||||
className={buttonClass(active)}
|
||||
onClick={() => dispatch(commandIds.workspaceSetPanel, { panel: active ? "none" : operation })}
|
||||
>
|
||||
<Icon size={24} weight={active ? "fill" : "regular"} />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
function iconForTool(tool: ToolId) {
|
||||
switch (tool) {
|
||||
case "generate":
|
||||
return Sparkle;
|
||||
case "brush":
|
||||
return PaintBrush;
|
||||
case "eraser":
|
||||
return Eraser;
|
||||
case "chromaKey":
|
||||
return DropHalf;
|
||||
case "magicWand":
|
||||
return MagicWand;
|
||||
case "pan":
|
||||
@@ -58,6 +74,14 @@ function iconForTool(tool: ToolId) {
|
||||
}
|
||||
}
|
||||
|
||||
function iconForOperation(operation: OperationId) {
|
||||
return operation === "generate" ? Sparkle : DropHalf;
|
||||
}
|
||||
|
||||
function labelForOperation(operation: OperationId) {
|
||||
return operation === "generate" ? "Generate" : "Chroma key";
|
||||
}
|
||||
|
||||
function isToolHighlighted(tool: ToolId, activeTool: ToolId, interactionMode: InteractionMode) {
|
||||
if (interactionMode.type === "temporary-pan") return tool === "pan";
|
||||
return activeTool === tool;
|
||||
|
||||
@@ -7,6 +7,6 @@ export function canvasCursorClass(interactionMode: InteractionMode, isPanning: b
|
||||
if (!canBrush) return "cursor-not-allowed";
|
||||
return hasBrushPreview ? "cursor-none" : "cursor-crosshair";
|
||||
}
|
||||
if (interactionMode.type === "tool" && (interactionMode.tool === "chromaKey" || interactionMode.tool === "magicWand")) return "cursor-crosshair";
|
||||
if (interactionMode.type === "tool" && interactionMode.tool === "magicWand") return "cursor-crosshair";
|
||||
return "cursor-default";
|
||||
}
|
||||
|
||||
@@ -62,10 +62,7 @@ export function createPaletteItems(options: {
|
||||
subtitle: tool === tools.activeTool ? "Current tool" : undefined,
|
||||
keywords: [tool],
|
||||
icon: toolIcon(tool),
|
||||
run: () => {
|
||||
if (tool === "generate") openGenerate();
|
||||
else dispatch(commandIds.toolSetActive, { tool });
|
||||
},
|
||||
run: () => dispatch(commandIds.toolSetActive, { tool }),
|
||||
})),
|
||||
);
|
||||
|
||||
@@ -214,11 +211,18 @@ export function createPaletteItems(options: {
|
||||
id: "open-generate",
|
||||
section: "Generate",
|
||||
title: "Open generate panel",
|
||||
subtitle: tools.activeTool === "generate" ? "Current tool" : undefined,
|
||||
keywords: ["ai"],
|
||||
icon: <Sparkle size={20} />,
|
||||
run: openGenerate,
|
||||
},
|
||||
{
|
||||
id: "open-chroma-key",
|
||||
section: "Masks",
|
||||
title: "Open chroma key operation",
|
||||
keywords: ["key", "mask", "remove background"],
|
||||
icon: <DropHalf size={20} />,
|
||||
run: () => dispatch(commandIds.workspaceSetPanel, { panel: "chromaKey" }),
|
||||
},
|
||||
...generateModeItems.map((modeItem) => ({
|
||||
id: `generate-mode-${modeItem.mode}`,
|
||||
section: "Generate",
|
||||
@@ -342,14 +346,10 @@ function toolIcon(tool: ToolId) {
|
||||
switch (tool) {
|
||||
case "select":
|
||||
return <Cursor size={20} />;
|
||||
case "generate":
|
||||
return <Sparkle size={20} />;
|
||||
case "brush":
|
||||
return <PaintBrush size={20} />;
|
||||
case "eraser":
|
||||
return <Eraser size={20} />;
|
||||
case "chromaKey":
|
||||
return <DropHalf size={20} />;
|
||||
case "magicWand":
|
||||
return <MagicWand size={20} />;
|
||||
case "pan":
|
||||
|
||||
@@ -2,12 +2,8 @@ import type { ToolId } from "@editor/tools";
|
||||
|
||||
export function labelForTool(tool: ToolId): string {
|
||||
switch (tool) {
|
||||
case "generate":
|
||||
return "Generate";
|
||||
case "brush":
|
||||
return "Brush";
|
||||
case "chromaKey":
|
||||
return "Chroma key";
|
||||
case "magicWand":
|
||||
return "Magic wand";
|
||||
case "eraser":
|
||||
|
||||
Reference in New Issue
Block a user