fix: respect canvas toolbar toggles
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-05-08 12:56:19 +08:00
parent 1912bcd25b
commit b78a2701bb
5 changed files with 231 additions and 39 deletions

View File

@ -3257,7 +3257,7 @@ export function AdminSettingsClient() {
const updateCanvasToolbarItem = (
key: CanvasToolbarItemKey,
updates: Partial<{ label: string; iconUrl: string }>
updates: Partial<{ label: string; iconUrl: string; enabled: boolean }>
) => {
setSiteSettings((prev) => {
const normalized = normalizeCanvasToolbarConfig(prev.canvasToolbarConfig);
@ -11824,12 +11824,29 @@ export function AdminSettingsClient() {
updateCanvasToolbarItem(item.key, {
label: defaultItem.label,
iconUrl: "",
enabled: true,
})
}
>
默认
</Button>
</div>
<div className="flex items-center justify-between rounded-lg border bg-background/70 px-3 py-2">
<div className="space-y-0.5">
<div className="text-xs font-medium">启用此功能</div>
<div className="text-[11px] text-muted-foreground">
关闭后前端不再显示这个工具按钮
</div>
</div>
<Switch
checked={itemConfig.enabled !== false}
onCheckedChange={(checked) =>
updateCanvasToolbarItem(item.key, {
enabled: checked,
})
}
/>
</div>
<div className="space-y-1.5">
<Label
className="text-xs text-muted-foreground"

View File

@ -108,6 +108,7 @@ import {
} from "@/lib/canvas/node-content-scale";
import {
getCanvasToolbarItem,
isCanvasToolbarItemEnabled,
type CanvasToolbarConfig,
} from "@/lib/canvas-toolbar-config";
import { cn, fetcher } from "@/lib/utils";
@ -225,6 +226,7 @@ interface BoxCutoutPoint {
const BOX_CUTOUT_DEFAULT_PADDING_RATIO = 0.08;
const BOX_CUTOUT_MIN_EDGE_PX = 24;
const FLOATING_TOOLBAR_SCALE = 0.8;
const IMAGE_EDIT_PANEL_SCALE = 0.5;
const DEFAULT_CROP_PRESET_ID = "1:1";
const IMAGE_ADJUST_NOISE_DATA_URI = `data:image/svg+xml;utf8,${encodeURIComponent(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='1.18' numOctaves='2' stitchTiles='stitch'/></filter><rect width='160' height='160' filter='url(#n)' opacity='1'/></svg>"
@ -1310,6 +1312,98 @@ export function ImageNode({
canvasToolbarConfig,
"imageCancelCutout"
);
const imageNodeMoreEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageNodeMore"
);
const imageRemoveBackgroundEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageRemoveBackground"
);
const imageRemoveWatermarkEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageRemoveWatermark"
);
const imageEnhanceEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageEnhance"
);
const imageNoteEnabled = isCanvasToolbarItemEnabled(canvasToolbarConfig, "imageNote");
const imageLayerSplitEnabledByToolbar = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageLayerSplit"
);
const imageResizeEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageResize"
);
const imageMultiAngleEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageMultiAngle"
);
const imagePromptRegenerateEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imagePromptRegenerate"
);
const imageTextEditEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageTextEdit"
);
const imageExtendEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageExtend"
);
const imageCropEnabled = isCanvasToolbarItemEnabled(canvasToolbarConfig, "imageCrop");
const imageAdjustEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageAdjust"
);
const imageFlipRotateEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageFlipRotate"
);
const imageRenameEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageRename"
);
const imageDownloadEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageDownload"
);
const imageDeleteEnabled = isCanvasToolbarItemEnabled(
canvasToolbarConfig,
"imageDelete"
);
const hasHoverImageMorePrimaryActions =
imageRemoveBackgroundEnabled ||
imageRemoveWatermarkEnabled ||
imageEnhanceEnabled ||
imageNoteEnabled ||
(isLayerSplitEnabled && imageLayerSplitEnabledByToolbar) ||
imageResizeEnabled ||
imageMultiAngleEnabled ||
imagePromptRegenerateEnabled ||
imageTextEditEnabled ||
imageExtendEnabled ||
imageCropEnabled ||
imageAdjustEnabled ||
imageFlipRotateEnabled ||
imageRenameEnabled;
const hasHoverImageMoreSecondaryActions =
imageDownloadEnabled || imageDeleteEnabled;
const hasHoverImageMoreMenu =
imageNodeMoreEnabled &&
(hasHoverImageMorePrimaryActions || hasHoverImageMoreSecondaryActions);
const hasSelectedImageMorePrimaryActions =
imageExtendEnabled ||
imageCropEnabled ||
imageAdjustEnabled ||
imageFlipRotateEnabled ||
imagePromptRegenerateEnabled ||
imageNoteEnabled;
const hasSelectedImageMoreMenu =
imageNodeMoreEnabled &&
(hasSelectedImageMorePrimaryActions || imageDeleteEnabled);
const contentScale = useMemo(
() =>
getNodeContentScale(node.size, {
@ -3181,8 +3275,8 @@ export function ImageNode({
}
const rect = containerRef.current.getBoundingClientRect();
const panelWidth = 520;
const panelHeight = 760;
const panelWidth = 520 * IMAGE_EDIT_PANEL_SCALE;
const panelHeight = 760 * IMAGE_EDIT_PANEL_SCALE;
const spacing = 18;
const maxTop = Math.max(12, window.innerHeight - panelHeight);
@ -3201,8 +3295,8 @@ export function ImageNode({
}
const rect = containerRef.current.getBoundingClientRect();
const panelWidth = 500;
const panelHeight = 980;
const panelWidth = 474 * IMAGE_EDIT_PANEL_SCALE;
const panelHeight = 980 * IMAGE_EDIT_PANEL_SCALE;
const spacing = 16;
const maxTop = Math.max(12, window.innerHeight - panelHeight);
@ -3217,12 +3311,12 @@ export function ImageNode({
const getFlipRotatePanelPosition = () => {
if (!containerRef.current || typeof window === "undefined") {
return { top: 0, left: 0, maxWidth: 660 };
return { top: 0, left: 0, maxWidth: 660 * IMAGE_EDIT_PANEL_SCALE };
}
const rect = containerRef.current.getBoundingClientRect();
const panelWidth = 640;
const panelHeight = 84;
const panelWidth = 640 * IMAGE_EDIT_PANEL_SCALE;
const panelHeight = 84 * IMAGE_EDIT_PANEL_SCALE;
const gap = 18;
const titleSafeHeight = 34;
const { viewportPadding, safeLeft, safeRight, maxTop } =
@ -3275,8 +3369,8 @@ export function ImageNode({
}
const rect = containerRef.current.getBoundingClientRect();
const panelWidth = 380;
const panelHeight = 760;
const panelWidth = 372 * IMAGE_EDIT_PANEL_SCALE;
const panelHeight = 760 * IMAGE_EDIT_PANEL_SCALE;
const spacing = 16;
const maxTop = Math.max(12, window.innerHeight - panelHeight);
@ -6533,7 +6627,11 @@ export function ImageNode({
{/* 工具栏使用 Portal 渲染到 body,避免被裁剪 */}
{/* 悬浮时的更多操作按钮 (右上角) */}
{isHovered && data.src && !isProcessing && !isSelected && (
{isHovered &&
data.src &&
!isProcessing &&
!isSelected &&
hasHoverImageMoreMenu ? (
<div className="absolute top-2 right-2 flex items-center gap-1">
<DropdownMenu>
<DropdownMenuTrigger asChild>
@ -6551,6 +6649,7 @@ export function ImageNode({
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{imageRemoveBackgroundEnabled ? (
<DropdownMenuItem onClick={() => handleAction("rmbg")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6559,6 +6658,8 @@ export function ImageNode({
/>
{imageRemoveBackgroundItem.label}
</DropdownMenuItem>
) : null}
{imageRemoveWatermarkEnabled ? (
<DropdownMenuItem onClick={() => handleAction("watermark")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6567,6 +6668,8 @@ export function ImageNode({
/>
{imageRemoveWatermarkItem.label}
</DropdownMenuItem>
) : null}
{imageEnhanceEnabled ? (
<DropdownMenuItem onClick={() => handleAction("enhance")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6575,6 +6678,8 @@ export function ImageNode({
/>
{imageEnhanceItem.label}
</DropdownMenuItem>
) : null}
{imageNoteEnabled ? (
<DropdownMenuItem onClick={() => handleAction("note")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6583,7 +6688,8 @@ export function ImageNode({
/>
{imageNoteItem.label}
</DropdownMenuItem>
{isLayerSplitEnabled ? (
) : null}
{isLayerSplitEnabled && imageLayerSplitEnabledByToolbar ? (
<DropdownMenuItem onClick={() => handleAction("layer-split")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6593,6 +6699,7 @@ export function ImageNode({
{imageLayerSplitItem.label}
</DropdownMenuItem>
) : null}
{imageResizeEnabled ? (
<DropdownMenuItem onClick={() => handleAction("resize")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6601,6 +6708,8 @@ export function ImageNode({
/>
{imageResizeItem.label}
</DropdownMenuItem>
) : null}
{imageMultiAngleEnabled ? (
<DropdownMenuItem onClick={() => handleAction("angle")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6609,6 +6718,8 @@ export function ImageNode({
/>
{imageMultiAngleItem.label}
</DropdownMenuItem>
) : null}
{imagePromptRegenerateEnabled ? (
<DropdownMenuItem
onClick={() => handleAction("prompt-regenerate")}
>
@ -6619,6 +6730,8 @@ export function ImageNode({
/>
{imagePromptRegenerateItem.label}
</DropdownMenuItem>
) : null}
{imageTextEditEnabled ? (
<DropdownMenuItem onClick={() => handleAction("text-edit")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6627,6 +6740,8 @@ export function ImageNode({
/>
{imageTextEditItem.label}
</DropdownMenuItem>
) : null}
{imageExtendEnabled ? (
<DropdownMenuItem onClick={() => handleAction("extend")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6635,6 +6750,8 @@ export function ImageNode({
/>
{imageExtendItem.label}
</DropdownMenuItem>
) : null}
{imageCropEnabled ? (
<DropdownMenuItem onClick={() => handleAction("crop")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6643,6 +6760,8 @@ export function ImageNode({
/>
{imageCropItem.label}
</DropdownMenuItem>
) : null}
{imageAdjustEnabled ? (
<DropdownMenuItem onClick={() => handleAction("adjust")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6651,6 +6770,8 @@ export function ImageNode({
/>
{imageAdjustItem.label}
</DropdownMenuItem>
) : null}
{imageFlipRotateEnabled ? (
<DropdownMenuItem onClick={() => handleAction("flip-rotate")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6659,6 +6780,8 @@ export function ImageNode({
/>
{imageFlipRotateItem.label}
</DropdownMenuItem>
) : null}
{imageRenameEnabled ? (
<DropdownMenuItem
onClick={() => {
setDraftTitle(displayTitle);
@ -6672,7 +6795,12 @@ export function ImageNode({
/>
{imageRenameItem.label}
</DropdownMenuItem>
) : null}
{hasHoverImageMorePrimaryActions &&
hasHoverImageMoreSecondaryActions ? (
<DropdownMenuSeparator />
) : null}
{imageDownloadEnabled ? (
<DropdownMenuItem onClick={handleDownload}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -6681,6 +6809,8 @@ export function ImageNode({
/>
{imageDownloadItem.label}
</DropdownMenuItem>
) : null}
{imageDeleteEnabled ? (
<DropdownMenuItem
className="text-destructive"
onClick={() => deleteNode(node.id)}
@ -6692,10 +6822,11 @@ export function ImageNode({
/>
{imageDeleteItem.label}
</DropdownMenuItem>
) : null}
</DropdownMenuContent>
</DropdownMenu>
</div>
)}
) : null}
{/* 提示词显示 */}
{data.prompt && isHovered && !isProcessing && (
@ -7131,6 +7262,7 @@ export function ImageNode({
<div className="mx-1 h-6 w-px bg-slate-200 dark:bg-slate-700" />
{hasSelectedImageMoreMenu ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
@ -7152,6 +7284,7 @@ export function ImageNode({
className="bg-white opacity-100 backdrop-blur-none dark:bg-slate-900 dark:text-slate-100"
side="bottom"
>
{imageExtendEnabled ? (
<DropdownMenuItem onClick={() => handleAction("extend")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -7160,6 +7293,8 @@ export function ImageNode({
/>
{imageExtendItem.label}
</DropdownMenuItem>
) : null}
{imageCropEnabled ? (
<DropdownMenuItem onClick={() => handleAction("crop")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -7168,6 +7303,8 @@ export function ImageNode({
/>
{imageCropItem.label}
</DropdownMenuItem>
) : null}
{imageAdjustEnabled ? (
<DropdownMenuItem onClick={() => handleAction("adjust")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -7176,6 +7313,8 @@ export function ImageNode({
/>
{imageAdjustItem.label}
</DropdownMenuItem>
) : null}
{imageFlipRotateEnabled ? (
<DropdownMenuItem onClick={() => handleAction("flip-rotate")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -7184,6 +7323,8 @@ export function ImageNode({
/>
{imageFlipRotateItem.label}
</DropdownMenuItem>
) : null}
{imagePromptRegenerateEnabled ? (
<DropdownMenuItem
onClick={() => handleAction("prompt-regenerate")}
>
@ -7194,6 +7335,8 @@ export function ImageNode({
/>
{imagePromptRegenerateItem.label}
</DropdownMenuItem>
) : null}
{imageNoteEnabled ? (
<DropdownMenuItem onClick={() => handleAction("note")}>
<CanvasToolbarIcon
className="mr-2 h-4 w-4"
@ -7202,7 +7345,11 @@ export function ImageNode({
/>
{imageNoteItem.label}
</DropdownMenuItem>
) : null}
{hasSelectedImageMorePrimaryActions && imageDeleteEnabled ? (
<DropdownMenuSeparator />
) : null}
{imageDeleteEnabled ? (
<DropdownMenuItem
className="text-red-600 focus:text-red-700"
onClick={() => deleteNode(node.id)}
@ -7214,8 +7361,10 @@ export function ImageNode({
/>
{imageDeleteItem.label}
</DropdownMenuItem>
) : null}
</DropdownMenuContent>
</DropdownMenu>
) : null}
<Button
className="h-10 w-10 rounded-xl text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
@ -7358,6 +7507,8 @@ export function ImageNode({
style={{
top: cropPanelPosition.top,
left: cropPanelPosition.left,
transform: `scale(${IMAGE_EDIT_PANEL_SCALE})`,
transformOrigin: "top left",
}}
>
<div className="px-8 pt-8 pb-6">
@ -7513,6 +7664,8 @@ export function ImageNode({
style={{
top: imageAdjustPanelPosition.top,
left: imageAdjustPanelPosition.left,
transform: `scale(${IMAGE_EDIT_PANEL_SCALE})`,
transformOrigin: "top left",
}}
>
<div className="flex items-center justify-between px-8 pt-8 pb-5">
@ -7604,7 +7757,11 @@ export function ImageNode({
style={{
top: flipRotatePanelPosition.top,
left: flipRotatePanelPosition.left,
maxWidth: `${flipRotatePanelPosition.maxWidth}px`,
maxWidth: `${Math.floor(
flipRotatePanelPosition.maxWidth / IMAGE_EDIT_PANEL_SCALE
)}px`,
transform: `scale(${IMAGE_EDIT_PANEL_SCALE})`,
transformOrigin: "top left",
}}
>
<div className="flex h-[82px] shrink-0 items-center gap-3 px-7">
@ -7712,6 +7869,8 @@ export function ImageNode({
style={{
top: anglePanelPosition.top,
left: anglePanelPosition.left,
transform: `scale(${IMAGE_EDIT_PANEL_SCALE})`,
transformOrigin: "top left",
}}
>
<div className="flex items-center justify-between">

View File

@ -102,18 +102,24 @@ type FloatingSidebarRailProps = {
children: ReactNode;
className?: string;
orientation?: "vertical" | "horizontal";
tone?: "dark" | "light";
};
export function FloatingSidebarRail({
children,
className,
orientation = "vertical",
tone = "dark",
}: FloatingSidebarRailProps) {
const isHorizontal = orientation === "horizontal";
const isLight = tone === "light";
return (
<div
className={cn(
"pointer-events-auto flex items-center gap-1 border border-white/8 bg-[#17181d]/94 text-white shadow-[0_22px_60px_rgba(6,8,14,0.34),inset_0_1px_0_rgba(255,255,255,0.05)] backdrop-blur-2xl",
"pointer-events-auto flex items-center gap-1 backdrop-blur-2xl",
isLight
? "border border-black/8 bg-white/96 text-[#111827] shadow-[0_20px_46px_rgba(15,23,42,0.12)]"
: "border border-white/8 bg-[#17181d]/94 text-white shadow-[0_22px_60px_rgba(6,8,14,0.34),inset_0_1px_0_rgba(255,255,255,0.05)]",
isHorizontal
? "h-[56px] w-auto flex-row overflow-hidden rounded-[22px] px-3 py-2"
: "w-[60px] flex-col overflow-y-auto rounded-[28px] px-2 py-2.5",

View File

@ -443,12 +443,12 @@ export function LovartHome({
!siteLogoDark?.trim() &&
Boolean(siteLogo?.trim());
const sidebarPopupBaseClass =
"rounded-[24px] border border-white/10 bg-[#17181d]/96 p-2.5 text-white shadow-[0_24px_64px_rgba(6,8,14,0.4),inset_0_1px_0_rgba(255,255,255,0.04)] backdrop-blur-2xl";
"rounded-[24px] border border-black/8 bg-white/96 p-2.5 text-[#111827] shadow-[0_24px_64px_rgba(15,23,42,0.12)] backdrop-blur-xl";
const lovartSidebarButtonClass = (active = false) =>
cn(
"group flex h-11 w-11 items-center justify-center rounded-[16px] text-white/52 transition-all duration-200 hover:bg-white/[0.07] hover:text-white/88",
"group flex h-11 w-11 items-center justify-center rounded-[16px] text-[#6b7280] transition-all duration-200 hover:bg-black/[0.04] hover:text-[#111827]",
active &&
"bg-white/[0.1] text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.08),0_10px_22px_rgba(0,0,0,0.22)]"
"bg-[#111827] text-white shadow-[0_10px_22px_rgba(15,23,42,0.18)]"
);
const [prompt, setPrompt] = useState("");
const [files, setFiles] = useState<UploadFileItem[]>([]);
@ -1381,8 +1381,8 @@ export function LovartHome({
<SidebarSvgIcon
iconUrl={item.iconUrl}
fallback={FallbackIcon}
imgClassName={cn(sizeClassName, "object-contain", "brightness-0")}
fallbackClassName={cn(sizeClassName, "text-black")}
imgClassName={cn(sizeClassName, "object-contain", "brightness-0 invert")}
fallbackClassName={cn(sizeClassName, "text-white")}
/>
);
}
@ -1394,12 +1394,11 @@ export function LovartHome({
imgClassName={cn(
sizeClassName,
"object-contain",
"brightness-0 invert",
options?.active ? "opacity-100" : "opacity-56"
options?.active ? "brightness-0 invert opacity-100" : "brightness-0 opacity-60"
)}
fallbackClassName={cn(
sizeClassName,
options?.active ? "text-white" : "text-white/60"
options?.active ? "text-white" : "text-[#6b7280]"
)}
/>
);
@ -1718,10 +1717,10 @@ export function LovartHome({
<button
type="button"
onClick={openNewCanvas}
className="flex h-[60px] w-[60px] shrink-0 items-center justify-center rounded-[22px] border border-white/8 bg-[#17181d]/96 shadow-[0_22px_60px_rgba(6,8,14,0.34),inset_0_1px_0_rgba(255,255,255,0.05)] backdrop-blur-2xl transition-transform duration-200 hover:-translate-y-0.5 hover:bg-[#1b1d23]"
className="flex h-[60px] w-[60px] shrink-0 items-center justify-center rounded-[22px] border border-black/8 bg-white/96 shadow-[0_20px_46px_rgba(15,23,42,0.12)] backdrop-blur-xl transition-transform duration-200 hover:-translate-y-0.5 hover:bg-white"
title={getLovartSidebarItem("newProject").label}
>
<div className="flex h-10 w-10 items-center justify-center rounded-[14px] bg-white text-black shadow-[0_10px_18px_rgba(255,255,255,0.15)]">
<div className="flex h-10 w-10 items-center justify-center rounded-[14px] bg-[#111827] text-white shadow-[0_10px_20px_rgba(15,23,42,0.18)]">
{renderLovartSidebarIcon("newProject", {
filled: true,
sizeClassName: "h-[18px] w-[18px]",
@ -1734,7 +1733,7 @@ export function LovartHome({
</TooltipContent>
</Tooltip>
) : null}
<FloatingSidebarRail className="max-h-[calc(100vh-172px)]">
<FloatingSidebarRail className="max-h-[calc(100vh-172px)]" tone="light">
{isLovartSidebarItemEnabled("home") ? (
<Tooltip>
<TooltipTrigger asChild>
@ -1827,7 +1826,7 @@ export function LovartHome({
align="start"
className={`w-[300px] ${sidebarPopupBaseClass}`}
>
<div className="px-2 pb-1 pt-1 text-xs font-medium text-white/42">
<div className="px-2 pb-1 pt-1 text-xs font-medium text-black/40">
{getLovartSidebarItem("tutorial").label}
</div>
<div className="max-h-[380px] space-y-1 overflow-y-auto">
@ -1836,10 +1835,10 @@ export function LovartHome({
<button
key={video.id}
type="button"
className="flex w-full items-center gap-2.5 rounded-2xl p-2.5 text-left transition hover:bg-white/[0.06]"
className="flex w-full items-center gap-2.5 rounded-2xl p-2.5 text-left transition hover:bg-black/[0.04]"
onClick={() => handleOpenTutorial(video)}
>
<div className="h-10 w-16 flex-shrink-0 overflow-hidden rounded-xl bg-white/[0.06]">
<div className="h-10 w-16 flex-shrink-0 overflow-hidden rounded-xl bg-black/[0.04]">
{video.coverUrl ? (
<img
src={video.coverUrl}
@ -1848,16 +1847,16 @@ export function LovartHome({
/>
) : (
<div className="flex h-full w-full items-center justify-center">
<PlayCircle className="h-4 w-4 text-white/40" />
<PlayCircle className="h-4 w-4 text-black/35" />
</div>
)}
</div>
<div className="min-w-0 flex-1">
<div className="truncate text-[13px] font-medium text-white/88">
<div className="truncate text-[13px] font-medium text-black/85">
{video.title}
</div>
{video.description ? (
<div className="truncate text-[11px] text-white/42">
<div className="truncate text-[11px] text-black/42">
{video.description}
</div>
) : null}
@ -1865,7 +1864,7 @@ export function LovartHome({
</button>
))
) : (
<div className="px-2 py-3 text-[12px] text-white/42">暂无教程视频</div>
<div className="px-2 py-3 text-[12px] text-black/42">暂无教程视频</div>
)}
</div>
</HoverCardContent>
@ -1994,7 +1993,7 @@ export function LovartHome({
align="start"
className={`w-[248px] ${sidebarPopupBaseClass}`}
>
<div className="px-2 pb-1 pt-1 text-xs font-medium text-white/42">
<div className="px-2 pb-1 pt-1 text-xs font-medium text-black/40">
{getLovartSidebarItem("toolbox1").label}
</div>
<div className="space-y-1">
@ -2002,11 +2001,11 @@ export function LovartHome({
<button
key={link.id}
type="button"
className="flex w-full items-center justify-between rounded-2xl px-3 py-2.5 text-left text-[13px] text-white/82 transition hover:bg-white/[0.06]"
className="flex w-full items-center justify-between rounded-2xl px-3 py-2.5 text-left text-[13px] text-black/80 transition hover:bg-black/[0.04]"
onClick={() => openNavLink(link.url)}
>
<span className="line-clamp-1">{link.name}</span>
<ArrowRight className="h-3.5 w-3.5 text-white/30" />
<ArrowRight className="h-3.5 w-3.5 text-black/30" />
</button>
))}
</div>
@ -2031,7 +2030,7 @@ export function LovartHome({
align="start"
className={`w-[248px] ${sidebarPopupBaseClass}`}
>
<div className="px-2 pb-1 pt-1 text-xs font-medium text-white/42">
<div className="px-2 pb-1 pt-1 text-xs font-medium text-black/40">
{getLovartSidebarItem("toolbox2").label}
</div>
<div className="space-y-1">
@ -2039,11 +2038,11 @@ export function LovartHome({
<button
key={link.id}
type="button"
className="flex w-full items-center justify-between rounded-2xl px-3 py-2.5 text-left text-[13px] text-white/82 transition hover:bg-white/[0.06]"
className="flex w-full items-center justify-between rounded-2xl px-3 py-2.5 text-left text-[13px] text-black/80 transition hover:bg-black/[0.04]"
onClick={() => openNavLink(link.url)}
>
<span className="line-clamp-1">{link.name}</span>
<ArrowRight className="h-3.5 w-3.5 text-white/30" />
<ArrowRight className="h-3.5 w-3.5 text-black/30" />
</button>
))}
</div>

View File

@ -699,6 +699,7 @@ export type CanvasToolbarItemKey =
export type CanvasToolbarItemConfig = {
label: string;
iconUrl: string;
enabled: boolean;
};
export type CanvasToolbarConfig = {
@ -710,6 +711,7 @@ const DEFAULT_ITEM_BY_KEY = DEFAULT_CANVAS_TOOLBAR_ITEMS.reduce(
acc[item.key] = {
label: item.label,
iconUrl: "",
enabled: true,
};
return acc;
},
@ -750,6 +752,8 @@ export function normalizeCanvasToolbarConfig(
items[defaultItem.key] = {
label,
iconUrl,
enabled:
typeof rawItem.enabled === "boolean" ? rawItem.enabled : true,
};
});
@ -768,3 +772,10 @@ export function getCanvasToolbarItem(
): CanvasToolbarItemConfig {
return config?.items?.[key] || DEFAULT_ITEM_BY_KEY[key];
}
export function isCanvasToolbarItemEnabled(
config: CanvasToolbarConfig | null | undefined,
key: CanvasToolbarItemKey
): boolean {
return getCanvasToolbarItem(config, key).enabled !== false;
}