fix: move flow board toolbar to sidebar
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-12 00:03:21 +08:00
parent 0870739a7d
commit 3c45bdfef9

View File

@ -31,7 +31,6 @@ import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry";
import {
CheckCircle2,
ChevronDown,
ChevronLeft,
FileText,
FolderOpen,
Flag,
@ -47,6 +46,12 @@ import {
X,
} from "lucide-react";
import { useTheme } from "next-themes";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
type ChangeEvent,
type ClipboardEvent,
@ -116,6 +121,10 @@ const FLOW_BOARD_IMAGE_UPLOAD_TYPES = new Set([
"image/png",
"image/webp",
]);
const FLOW_BOARD_SIDEBAR_BUTTON_CLASS =
"flex h-10 w-10 items-center justify-center rounded-full text-[#4f566b] transition-colors hover:bg-black/5 hover:text-[#111827] disabled:cursor-not-allowed disabled:opacity-45";
const FLOW_BOARD_SIDEBAR_BUTTON_ACTIVE_CLASS =
"bg-[#111827] text-white shadow-sm hover:bg-[#1f2937] hover:text-white";
const FLOW_BOARD_MARKER_GROUPS: FlowBoardMarkerGroup[] = [
{
id: "tag",
@ -1374,132 +1383,154 @@ export function DrawnixWorkspace({
ref={mindMapFileInputRef}
type="file"
/>
<div className="pointer-events-none absolute top-4 left-4 z-20 flex items-center">
<div
className="pointer-events-auto flex h-11 items-center gap-3 rounded-2xl border border-black/5 bg-white px-3 shadow-sm transition-all hover:shadow-md"
data-flow-board-shell-control="true"
>
<button
className="group flex items-center gap-1 rounded-xl px-2 py-1.5 font-medium text-[#4f566b] text-sm transition-colors hover:bg-black/5 hover:text-[#111827]"
onClick={() => {
window.location.href = "/";
}}
type="button"
>
<ChevronLeft className="group-hover:-translate-x-0.5 h-4 w-4 transition-transform" />
返回
</button>
<div className="h-5 w-px bg-black/10" />
<div className="flex items-center gap-2.5 px-1">
{displayLogo ? (
<img
alt={siteName}
className="h-6 w-6 rounded-md object-cover shadow-sm"
src={displayLogo}
/>
) : (
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-[#111827] font-bold text-white text-xs shadow-sm">
{siteName.slice(0, 1).toUpperCase()}
</div>
)}
<div className="flex items-center gap-2">
<input
aria-label="流程白板文件名"
className="h-7 w-fit min-w-[120px] max-w-[200px] rounded-lg border border-transparent bg-transparent px-2 font-bold text-[#111827] text-sm tracking-wide outline-none transition focus:border-black/10 focus:bg-[#f7f8fb]"
onBlur={handleTitleCommit}
onChange={(event) => setCurrentTitle(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.currentTarget.blur();
}
}}
value={currentTitle}
/>
<span className="shrink-0 rounded-md bg-[#f7f8fb] px-1.5 py-0.5 font-medium text-[#7a8297] text-[11px]">
{siteName}
</span>
</div>
</div>
<div className="h-5 w-px bg-black/10" />
<div className="flex flex-1 items-center justify-between gap-2">
<div className="flex items-center gap-1">
<aside
className="pointer-events-none absolute top-4 left-4 z-20 flex flex-col items-center gap-2 lg:top-6 lg:left-6"
data-flow-board-shell-control="true"
>
<TooltipProvider delayDuration={120}>
<Tooltip>
<TooltipTrigger asChild>
<button
className="flex h-8 items-center gap-1.5 rounded-xl px-2.5 font-medium text-[#4f566b] text-xs transition-colors hover:bg-black/5 hover:text-[#111827]"
disabled={isSavingNow}
onClick={() => flushPendingSave(true)}
type="button"
>
{isSavingNow ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<Save className="h-3.5 w-3.5" />
)}
保存云端
</button>
<button
className="flex h-8 items-center gap-1.5 rounded-xl px-2.5 font-medium text-[#4f566b] text-xs transition-colors hover:bg-black/5 hover:text-[#111827]"
onClick={handleOpenFilePanel}
type="button"
>
<FolderOpen className="h-3.5 w-3.5" />
云文件
</button>
<button
className="flex h-8 items-center gap-1.5 rounded-xl px-2.5 font-medium text-[#4f566b] text-xs transition-colors hover:bg-black/5 hover:text-[#111827] disabled:cursor-not-allowed disabled:opacity-50"
disabled={isSavingNow || isImportingMindMap}
onClick={() => mindMapFileInputRef.current?.click()}
type="button"
>
{isImportingMindMap ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<Upload className="h-3.5 w-3.5" />
)}
导入
</button>
<button
className={`flex h-8 items-center gap-1.5 rounded-xl px-2.5 font-medium text-xs transition-colors ${
isMarkerPanelOpen
? "bg-[#111827] text-white shadow-sm hover:bg-[#1f2937]"
: "text-[#4f566b] hover:bg-black/5 hover:text-[#111827]"
}`}
className="pointer-events-auto flex h-[52px] w-[52px] items-center justify-center gap-3 rounded-full border border-black/10 bg-white px-2.5 text-left shadow transition hover:bg-black/[0.03] hover:shadow-md lg:w-[164px] lg:justify-start lg:px-4"
onClick={() => {
setIsFilePanelOpen(false);
setIsMarkerPanelOpen((open) => !open);
refreshMarkerSelectionState();
window.location.href = "/";
}}
title="返回首页"
type="button"
>
<Tag className="h-3.5 w-3.5" />
标记
{displayLogo ? (
<img
alt={siteName}
className="h-8 w-8 rounded-full object-cover"
src={displayLogo}
/>
) : (
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-black font-bold text-white text-sm">
{siteName.slice(0, 1).toUpperCase()}
</div>
)}
<span className="hidden min-w-0 truncate font-semibold text-[#111827] text-[15px] leading-none tracking-tight lg:block">
{siteName}
</span>
</button>
</TooltipTrigger>
<TooltipContent side="right">返回首页</TooltipContent>
</Tooltip>
<button
className="flex h-8 items-center gap-1.5 rounded-xl px-2.5 font-medium text-[#4f566b] text-xs transition-colors hover:bg-black/5 hover:text-[#111827]"
onClick={handleCreateCloudFile}
type="button"
>
<Plus className="h-3.5 w-3.5" />
新建
</button>
</div>
<div className="flex shrink-0 items-center gap-1.5 pl-2 text-[#7a8297]">
<Save className="h-3.5 w-3.5" />
<span className="whitespace-nowrap font-medium text-xs">
{formatSavedAt(lastSavedAt)}
</span>
<div className="pointer-events-auto hidden w-[164px] rounded-[22px] border border-black/10 bg-white px-3 py-2 shadow lg:block">
<div className="mb-1 px-1 font-medium text-[#7a8297] text-[10px] uppercase tracking-[0.18em]">
流程白板
</div>
<input
aria-label="流程白板文件名"
className="h-8 w-full rounded-xl border border-transparent bg-transparent px-1 font-semibold text-[#111827] text-sm tracking-wide outline-none transition focus:border-black/10 focus:bg-[#f7f8fb]"
onBlur={handleTitleCommit}
onChange={(event) => setCurrentTitle(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.currentTarget.blur();
}
}}
value={currentTitle}
/>
</div>
</div>
</div>
<div className="pointer-events-auto flex max-h-[calc(100vh-148px)] w-[56px] flex-col items-center gap-1 overflow-y-auto rounded-[28px] border border-black/10 bg-white px-1.5 py-2.5 shadow lg:max-h-[calc(100vh-236px)]">
<Tooltip>
<TooltipTrigger asChild>
<button
className={FLOW_BOARD_SIDEBAR_BUTTON_CLASS}
disabled={isSavingNow}
onClick={() => flushPendingSave(true)}
title="保存云端"
type="button"
>
{isSavingNow ? (
<Loader2 className="h-[18px] w-[18px] animate-spin" />
) : (
<Save className="h-[18px] w-[18px]" />
)}
</button>
</TooltipTrigger>
<TooltipContent side="right">保存云端</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
className={FLOW_BOARD_SIDEBAR_BUTTON_CLASS}
onClick={handleOpenFilePanel}
title="云文件"
type="button"
>
<FolderOpen className="h-[18px] w-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent side="right">云文件</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
className={FLOW_BOARD_SIDEBAR_BUTTON_CLASS}
disabled={isSavingNow || isImportingMindMap}
onClick={() => mindMapFileInputRef.current?.click()}
title="导入"
type="button"
>
{isImportingMindMap ? (
<Loader2 className="h-[18px] w-[18px] animate-spin" />
) : (
<Upload className="h-[18px] w-[18px]" />
)}
</button>
</TooltipTrigger>
<TooltipContent side="right">导入</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
className={`${FLOW_BOARD_SIDEBAR_BUTTON_CLASS} ${
isMarkerPanelOpen ? FLOW_BOARD_SIDEBAR_BUTTON_ACTIVE_CLASS : ""
}`}
onClick={() => {
setIsFilePanelOpen(false);
setIsMarkerPanelOpen((open) => !open);
refreshMarkerSelectionState();
}}
title="标记"
type="button"
>
<Tag className="h-[18px] w-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent side="right">标记</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
className={FLOW_BOARD_SIDEBAR_BUTTON_CLASS}
onClick={handleCreateCloudFile}
title="新建"
type="button"
>
<Plus className="h-[18px] w-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent side="right">新建</TooltipContent>
</Tooltip>
</div>
<div className="pointer-events-auto hidden max-w-[164px] items-center gap-1.5 rounded-full border border-black/10 bg-white px-3 py-1.5 text-[#7a8297] shadow lg:flex">
<Save className="h-3.5 w-3.5 shrink-0" />
<span className="truncate font-medium text-[11px]">
{formatSavedAt(lastSavedAt)}
</span>
</div>
</TooltipProvider>
</aside>
{isFilePanelOpen ? (
<aside