|
<script lang="ts" context="module">
|
|
import type {
|
|
Subscriber,
|
|
Invalidator,
|
|
Unsubscriber,
|
|
Writable
|
|
} from "svelte/store";
|
|
import type { tool } from "../tools/";
|
|
|
|
export const TOOL_KEY = Symbol("tool");
|
|
|
|
type upload_tool = "bg_webcam" | "bg_upload" | "bg_clipboard" | "bg_color";
|
|
type transform_tool = "crop" | "rotate";
|
|
type brush_tool = "brush_color" | "brush_size";
|
|
type eraser_tool = "eraser_size";
|
|
|
|
export interface ToolOptions {
|
|
order: number;
|
|
label: string;
|
|
icon: typeof Image;
|
|
cb: (...args: any[]) => void;
|
|
id: upload_tool | transform_tool | brush_tool | eraser_tool;
|
|
}
|
|
|
|
export interface ToolMeta {
|
|
default: upload_tool | transform_tool | brush_tool | eraser_tool | null;
|
|
options: ToolOptions[];
|
|
}
|
|
|
|
export interface ToolContext {
|
|
register_tool: (type: tool, opts?: { cb: () => void }) => () => void;
|
|
active_tool: {
|
|
set: (tool: tool) => void;
|
|
subscribe(
|
|
this: void,
|
|
run: Subscriber<tool | null>,
|
|
invalidate?: Invalidator<tool | null>
|
|
): Unsubscriber;
|
|
};
|
|
|
|
current_color: Writable<string>;
|
|
}
|
|
</script>
|
|
|
|
<script lang="ts">
|
|
import { Toolbar, IconButton } from "@gradio/atoms";
|
|
import { getContext, setContext } from "svelte";
|
|
import { writable } from "svelte/store";
|
|
import { EDITOR_KEY, type EditorContext } from "../ImageEditor.svelte";
|
|
import { Image, Crop, Brush, Erase } from "@gradio/icons";
|
|
import { type I18nFormatter } from "@gradio/utils";
|
|
|
|
const { active_tool, toolbar_box, editor_box } =
|
|
getContext<EditorContext>(EDITOR_KEY);
|
|
|
|
export let i18n: I18nFormatter;
|
|
|
|
let tools: tool[] = [];
|
|
|
|
const cbs: Record<string, () => void> = {};
|
|
let current_color = writable("#000000");
|
|
const tool_context: ToolContext = {
|
|
current_color,
|
|
register_tool: (type: tool, opts?: { cb: () => void }) => {
|
|
tools = [...tools, type];
|
|
if (opts?.cb) {
|
|
cbs[type] = opts.cb;
|
|
}
|
|
|
|
return () => {
|
|
tools = tools.filter((tool) => tool !== type);
|
|
};
|
|
},
|
|
|
|
active_tool: {
|
|
subscribe: active_tool.subscribe,
|
|
set: active_tool.set
|
|
}
|
|
};
|
|
|
|
setContext<ToolContext>(TOOL_KEY, tool_context);
|
|
|
|
const tools_meta: Record<
|
|
tool,
|
|
{
|
|
order: number;
|
|
label: string;
|
|
icon: typeof Image;
|
|
}
|
|
> = {
|
|
crop: {
|
|
order: 1,
|
|
label: i18n("Transform"),
|
|
icon: Crop
|
|
},
|
|
draw: {
|
|
order: 2,
|
|
label: i18n("Draw"),
|
|
icon: Brush
|
|
},
|
|
erase: {
|
|
order: 2,
|
|
label: i18n("Erase"),
|
|
icon: Erase
|
|
},
|
|
bg: {
|
|
order: 0,
|
|
label: i18n("Background"),
|
|
icon: Image
|
|
}
|
|
} as const;
|
|
|
|
let toolbar_width: number;
|
|
let toolbar_wrap: HTMLDivElement;
|
|
|
|
$: toolbar_width, $editor_box, get_dimensions();
|
|
|
|
function get_dimensions(): void {
|
|
if (!toolbar_wrap) return;
|
|
$toolbar_box = toolbar_wrap.getBoundingClientRect();
|
|
}
|
|
|
|
function handle_click(e: Event, tool: tool): void {
|
|
e.stopPropagation();
|
|
$active_tool = tool;
|
|
cbs[tool] && cbs[tool]();
|
|
}
|
|
</script>
|
|
|
|
<slot />
|
|
|
|
<div
|
|
class="toolbar-wrap"
|
|
bind:clientWidth={toolbar_width}
|
|
bind:this={toolbar_wrap}
|
|
>
|
|
<Toolbar show_border={false}>
|
|
{#each tools as tool (tool)}
|
|
<IconButton
|
|
highlight={$active_tool === tool}
|
|
on:click={(e) => handle_click(e, tool)}
|
|
Icon={tools_meta[tool].icon}
|
|
size="medium"
|
|
padded={false}
|
|
label={tools_meta[tool].label + " button"}
|
|
transparent={true}
|
|
offset={tool === "draw" ? -2 : tool === "erase" ? -6 : 0}
|
|
/>
|
|
{/each}
|
|
</Toolbar>
|
|
</div>
|
|
|
|
<style>
|
|
.toolbar-wrap {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
margin-left: var(--spacing-xl);
|
|
height: 100%;
|
|
}
|
|
</style>
|
|
|