| <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, meta: ToolMeta) => () => void; | |
| active_tool: { | |
| subscribe( | |
| this: void, | |
| run: Subscriber<tool | null>, | |
| invalidate?: Invalidator<tool | null> | |
| ): Unsubscriber; | |
| }; | |
| activate_subtool: ( | |
| sub_tool: upload_tool | transform_tool | brush_tool | eraser_tool | null, | |
| cb?: (...args: any[]) => void | |
| ) => void; | |
| 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 { current_history, active_tool } = | |
| getContext<EditorContext>(EDITOR_KEY); | |
| export let i18n: I18nFormatter; | |
| let tools: tool[] = []; | |
| const metas: Record<tool, ToolMeta | null> = { | |
| draw: null, | |
| erase: null, | |
| crop: null, | |
| bg: null | |
| }; | |
| $: sub_menu = $active_tool && metas[$active_tool]; | |
| let current_color = writable("#000000"); | |
| let sub_tool: upload_tool | transform_tool | brush_tool | eraser_tool | null; | |
| const tool_context: ToolContext = { | |
| current_color, | |
| register_tool: (type: tool, meta: ToolMeta) => { | |
| tools = [...tools, type]; | |
| metas[type] = meta; | |
| return () => { | |
| tools = tools.filter((tool) => tool !== type); | |
| }; | |
| }, | |
| active_tool: { | |
| subscribe: active_tool.subscribe | |
| }, | |
| activate_subtool: ( | |
| _sub_tool: upload_tool | transform_tool | brush_tool | eraser_tool | null, | |
| cb?: (...args: any[]) => void | |
| ) => { | |
| sub_tool = _sub_tool; | |
| if (cb) cb(); | |
| } | |
| }; | |
| setContext<ToolContext>(TOOL_KEY, tool_context); | |
| const tools_meta: Record< | |
| tool, | |
| { | |
| order: number; | |
| label: string; | |
| icon: typeof Image; | |
| } | |
| > = { | |
| bg: { | |
| order: 0, | |
| label: i18n("Image"), | |
| icon: 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 | |
| } | |
| } as const; | |
| </script> | |
| <slot /> | |
| <div class="toolbar-wrap"> | |
| <Toolbar show_border={false}> | |
| {#if sub_menu} | |
| {#each sub_menu.options as meta (meta.id)} | |
| <IconButton | |
| highlight={sub_tool === meta.id && meta.id !== "brush_size"} | |
| color={$active_tool === "draw" && meta.id === "brush_size" | |
| ? $current_color | |
| : undefined} | |
| on:click={() => tool_context.activate_subtool(meta.id, meta.cb)} | |
| Icon={meta.icon} | |
| size="large" | |
| padded={false} | |
| label={meta.label + " button"} | |
| hasPopup={true} | |
| transparent={true} | |
| /> | |
| {/each} | |
| {/if} | |
| </Toolbar> | |
| <Toolbar show_border={false}> | |
| {#each tools as tool (tool)} | |
| <IconButton | |
| disabled={tool === "bg" && !!$current_history.previous} | |
| highlight={$active_tool === tool} | |
| on:click={() => ($active_tool = tool)} | |
| Icon={tools_meta[tool].icon} | |
| size="large" | |
| padded={false} | |
| label={tools_meta[tool].label + " button"} | |
| transparent={true} | |
| /> | |
| {/each} | |
| </Toolbar> | |
| </div> | |
| <style> | |
| .toolbar-wrap { | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| width: 100%; | |
| } | |
| </style> | |