fix: respect canvas toolbar toggles
This commit is contained in:
parent
1912bcd25b
commit
b78a2701bb
@ -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"
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user