mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-09 03:07:46 +08:00
The canvas now fills the window and the chat floats on its right. draw.io is served from our own origin (public/drawio, downloaded at build time and checked against the release's SHA-256), so the app can drive the editor directly: - every AI change becomes a version card with a thumbnail; compare, restore or undo it, and Ctrl+Z on the canvas takes an AI change back in one step - shapes the AI just changed are highlighted on the canvas - selected shapes appear as a chip and are sent to the model - the app's own canvas toolbar, shape library and page tabs replace draw.io's chrome - new start screen, session rename and switcher, open .drawio files - settings split into Models & keys, Appearance, Advanced and About - phones switch between canvas and chat - dark mode keeps the diagram on a white sheet and no longer flashes With an external draw.io (NEXT_PUBLIC_DRAWIO_BASE_URL) the app falls back to draw.io's own toolbar and the features above that need same origin are off. The chat logic moved from the 1500-line chat panel into a chat engine plus small UI components; zustand holds canvas, versions, settings and UI state.
81 lines
2.8 KiB
TypeScript
81 lines
2.8 KiB
TypeScript
"use client"
|
|
|
|
import type React from "react"
|
|
import { forwardRef } from "react"
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
interface IconButtonProps extends React.ComponentProps<"button"> {
|
|
label: string
|
|
/** Shown next to the label in the tooltip, e.g. "⌘B" */
|
|
shortcut?: string
|
|
active?: boolean
|
|
tooltipSide?: "top" | "bottom" | "left" | "right"
|
|
size?: "sm" | "md"
|
|
}
|
|
|
|
/** Square icon button with a tooltip; the tooltip text is also its label */
|
|
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
function IconButton(
|
|
{
|
|
label,
|
|
shortcut,
|
|
active = false,
|
|
tooltipSide = "bottom",
|
|
size = "md",
|
|
className,
|
|
children,
|
|
...props
|
|
},
|
|
ref,
|
|
) {
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<button
|
|
ref={ref}
|
|
type="button"
|
|
aria-label={label}
|
|
aria-pressed={active || undefined}
|
|
className={cn(
|
|
"inline-flex shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors",
|
|
"hover:bg-accent hover:text-foreground",
|
|
"focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring",
|
|
"disabled:pointer-events-none disabled:opacity-35",
|
|
"[&_svg]:size-4 [&_svg]:stroke-[1.75]",
|
|
size === "md" ? "size-8" : "size-7",
|
|
// A tinted tile: solid ink is for the main action
|
|
active &&
|
|
"bg-interactive-active text-foreground hover:bg-interactive-active [&_svg]:stroke-2",
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</button>
|
|
</TooltipTrigger>
|
|
<TooltipContent
|
|
side={tooltipSide}
|
|
className="flex items-center gap-2"
|
|
>
|
|
{label}
|
|
{shortcut && (
|
|
<kbd className="font-sans text-[11px] opacity-60">
|
|
{shortcut}
|
|
</kbd>
|
|
)}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
)
|
|
},
|
|
)
|
|
|
|
/** Thin vertical divider between toolbar groups */
|
|
export function ToolbarDivider() {
|
|
return <span className="mx-1 h-5 w-px shrink-0 bg-border" aria-hidden />
|
|
}
|