fix: compact canvas image tools
This commit is contained in:
parent
1a194b5527
commit
f34344d53b
@ -1284,14 +1284,14 @@ export function AiWashWorkspace({
|
||||
) : (
|
||||
<div className="text-center">
|
||||
<button
|
||||
className="inline-flex h-16 items-center rounded-[22px] bg-[#111111] px-9 text-[18px] font-semibold text-white shadow-[0_18px_42px_rgba(0,0,0,0.14)] transition hover:-translate-y-0.5 hover:bg-black"
|
||||
className="inline-flex h-12 items-center rounded-[18px] bg-[#111111] px-6 text-[15px] font-semibold text-white shadow-[0_12px_28px_rgba(0,0,0,0.12)] transition hover:-translate-y-0.5 hover:bg-black"
|
||||
onClick={() => onRequestAddImage?.()}
|
||||
type="button"
|
||||
>
|
||||
<ImageIcon className="mr-3 h-5 w-5" />
|
||||
<ImageIcon className="mr-2.5 h-4 w-4" />
|
||||
添加图片
|
||||
</button>
|
||||
<div className="mt-4 text-[14px] text-[#777777]">
|
||||
<div className="mt-2.5 text-[13px] text-[#777777]">
|
||||
选择本地图后即可开始 {config.modeLabel || "AI洗图"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1201,6 +1201,14 @@ export function ImageNode({
|
||||
imageNodeMoreEnabled &&
|
||||
(hasHoverImageMorePrimaryActions || hasHoverImageMoreSecondaryActions);
|
||||
const hasSelectedImageMorePrimaryActions =
|
||||
imageBoxCutoutEnabled ||
|
||||
imageLocalEditEnabled ||
|
||||
imageRemoveWatermarkEnabled ||
|
||||
imageResizeEnabled ||
|
||||
imageMultiAngleEnabled ||
|
||||
imageGenerate3dModelEnabled ||
|
||||
imageGenerate3dWorldEnabled ||
|
||||
imageLayerSplitEnabled ||
|
||||
imageExtendEnabled ||
|
||||
imageCropEnabled ||
|
||||
imageAdjustEnabled ||
|
||||
@ -1208,22 +1216,14 @@ export function ImageNode({
|
||||
imagePromptRegenerateEnabled ||
|
||||
imageNoteEnabled;
|
||||
const hasSelectedImageMoreMenu =
|
||||
imageNodeMoreEnabled &&
|
||||
(hasSelectedImageMorePrimaryActions || imageDeleteEnabled);
|
||||
hasSelectedImageMorePrimaryActions ||
|
||||
(imageNodeMoreEnabled && imageDeleteEnabled);
|
||||
const hasSelectedImagePrimaryToolbarActions =
|
||||
imagePickObjectEnabled ||
|
||||
imageEnhanceEnabled ||
|
||||
imageRemoveBackgroundEnabled ||
|
||||
imageBoxCutoutEnabled ||
|
||||
imageLocalEditEnabled ||
|
||||
imageRemoveWatermarkEnabled ||
|
||||
imageResizeEnabled ||
|
||||
imageTextEditEnabled ||
|
||||
imageMultiAngleEnabled ||
|
||||
imageGenerate3dModelEnabled ||
|
||||
imageGenerate3dWorldEnabled ||
|
||||
imageAiWashQuickEnabled ||
|
||||
imageLayerSplitEnabled;
|
||||
imageAiWashQuickEnabled;
|
||||
const hasSelectedImageSecondaryToolbarActions =
|
||||
hasSelectedImageMoreMenu || imageDownloadEnabled;
|
||||
const contentScale = useMemo(
|
||||
@ -4294,7 +4294,7 @@ export function ImageNode({
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
const toolbarWidth = Math.max(
|
||||
280,
|
||||
(toolbarSize.width || 980) * FLOATING_TOOLBAR_SCALE
|
||||
(toolbarSize.width || 640) * FLOATING_TOOLBAR_SCALE
|
||||
);
|
||||
const toolbarHeight = Math.max(
|
||||
42,
|
||||
@ -4302,12 +4302,12 @@ export function ImageNode({
|
||||
);
|
||||
const titleSafeHeight = 34;
|
||||
const gap = 12;
|
||||
const { viewportPadding, maxTop, safeLeft, safeRight } =
|
||||
const { viewportPadding, maxTop, safeBottom, safeLeft, safeRight } =
|
||||
getFloatingViewportBounds(toolbarHeight);
|
||||
|
||||
let top = rect.top - toolbarHeight - titleSafeHeight - gap;
|
||||
if (top < viewportPadding) {
|
||||
top = rect.bottom + gap;
|
||||
let top = rect.bottom + gap;
|
||||
if (top + toolbarHeight > safeBottom) {
|
||||
top = rect.top - toolbarHeight - titleSafeHeight - gap;
|
||||
}
|
||||
top = Math.min(Math.max(top, viewportPadding), maxTop);
|
||||
|
||||
@ -4332,36 +4332,31 @@ export function ImageNode({
|
||||
return { top: 0, left: 0, maxWidth: 480 };
|
||||
};
|
||||
|
||||
const getAiWashQuickPanelPosition = (
|
||||
anchor: { top: number; left: number; maxWidth: number }
|
||||
) => {
|
||||
if (typeof window === "undefined") {
|
||||
return { top: 0, left: 0, width: 360, maxHeight: 520 };
|
||||
const getAiWashQuickPanelPosition = () => {
|
||||
if (!containerRef.current || typeof window === "undefined") {
|
||||
return { top: 0, left: 0, width: 286, maxHeight: 430 };
|
||||
}
|
||||
|
||||
const panelWidth = 360;
|
||||
const panelHeight = 540;
|
||||
const gap = 10;
|
||||
const toolbarHeight = Math.max(
|
||||
42,
|
||||
(toolbarSize.height || 60) * FLOATING_TOOLBAR_SCALE
|
||||
);
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
const panelWidth = 286;
|
||||
const panelHeight = 430;
|
||||
const gap = 12;
|
||||
const { viewportPadding, safeBottom, safeLeft, safeRight, maxTop } =
|
||||
getFloatingViewportBounds(panelHeight);
|
||||
const width = Math.min(panelWidth, Math.max(260, safeRight - safeLeft));
|
||||
|
||||
let top = anchor.top + toolbarHeight + gap;
|
||||
let left = rect.right + gap;
|
||||
if (left + width > safeRight) {
|
||||
left = rect.left - width - gap;
|
||||
}
|
||||
left = Math.min(Math.max(left, safeLeft), Math.max(safeLeft, safeRight - width));
|
||||
|
||||
let top = rect.top;
|
||||
if (top + panelHeight > safeBottom) {
|
||||
top = anchor.top - panelHeight - gap;
|
||||
top = rect.bottom - panelHeight;
|
||||
}
|
||||
top = Math.min(Math.max(top, viewportPadding), maxTop);
|
||||
|
||||
const preferredLeft = anchor.left;
|
||||
const left = Math.min(
|
||||
Math.max(preferredLeft, safeLeft),
|
||||
Math.max(safeLeft, safeRight - width)
|
||||
);
|
||||
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
@ -7457,7 +7452,7 @@ export function ImageNode({
|
||||
const cropPanelPosition = getCropPanelPosition();
|
||||
const imageAdjustPanelPosition = getImageAdjustPanelPosition();
|
||||
const toolbarPosition = getToolbarPosition();
|
||||
const aiWashQuickPanelPosition = getAiWashQuickPanelPosition(toolbarPosition);
|
||||
const aiWashQuickPanelPosition = getAiWashQuickPanelPosition();
|
||||
const boxCutoutToolbarPosition = getBoxCutoutToolbarPosition();
|
||||
const flipRotatePanelPosition = getFlipRotatePanelPosition();
|
||||
const anglePanelPosition = getAnglePanelPosition();
|
||||
@ -8653,7 +8648,7 @@ export function ImageNode({
|
||||
typeof document !== "undefined" &&
|
||||
createPortal(
|
||||
<div
|
||||
className="fixed z-[10000] rounded-[28px] border border-slate-200 bg-white/96 p-3.5 text-slate-950 shadow-[0_20px_54px_rgba(15,23,42,0.16)] backdrop-blur-xl dark:border-white/12 dark:bg-slate-950/94 dark:text-white"
|
||||
className="fixed z-[10000] rounded-[20px] border border-slate-200 bg-white/96 p-2.5 text-slate-950 shadow-[0_14px_34px_rgba(15,23,42,0.15)] backdrop-blur-xl dark:border-white/12 dark:bg-slate-950/94 dark:text-white"
|
||||
data-canvas-no-drag="true"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
@ -8667,93 +8662,80 @@ export function ImageNode({
|
||||
scrollbarWidth: "thin",
|
||||
}}
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-2.5">
|
||||
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-[16px] border border-slate-200 bg-white shadow-[0_8px_18px_rgba(15,23,42,0.06)] dark:border-white/12 dark:bg-white/8">
|
||||
<div className="mb-2.5 flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-[13px] border border-slate-200 bg-white shadow-[0_6px_14px_rgba(15,23,42,0.06)] dark:border-white/12 dark:bg-white/8">
|
||||
<CanvasToolbarIcon
|
||||
className="h-5 w-5"
|
||||
className="h-4 w-4"
|
||||
fallback={Sparkles}
|
||||
iconUrl={aiWashQuickConfig.modeIconUrl}
|
||||
/>
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate font-semibold text-[16px] tracking-[-0.03em]">
|
||||
<div className="truncate font-semibold text-[14px] tracking-[-0.02em]">
|
||||
{aiWashQuickModeLabel}
|
||||
</div>
|
||||
<div className="mt-0.5 text-[12px] text-slate-500 dark:text-white/52">
|
||||
选中图片快捷洗图
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 transition hover:border-slate-950 hover:text-slate-950 dark:border-white/12 dark:bg-white/8 dark:text-white/60 dark:hover:border-white dark:hover:text-white"
|
||||
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 transition hover:border-slate-950 hover:text-slate-950 dark:border-white/12 dark:bg-white/8 dark:text-white/60 dark:hover:border-white dark:hover:text-white"
|
||||
onClick={() => setAiWashQuickPanelOpen(false)}
|
||||
title="关闭"
|
||||
type="button"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{aiWashConfigLoading ? (
|
||||
<div className="mb-3 flex items-center gap-2 rounded-[18px] border border-slate-200 bg-slate-50 px-3 py-2 text-[13px] text-slate-500 dark:border-white/10 dark:bg-white/6 dark:text-white/58">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<div className="mb-2 flex items-center gap-2 rounded-[14px] border border-slate-200 bg-slate-50 px-2.5 py-2 text-[12px] text-slate-500 dark:border-white/10 dark:bg-white/6 dark:text-white/58">
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
正在加载 AI洗图 配置...
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="rounded-[22px] border border-slate-200 bg-slate-50/70 p-3 dark:border-white/10 dark:bg-white/[0.04]">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2 text-[14px] font-semibold">
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
渲染目标
|
||||
<div className="space-y-2">
|
||||
<div className="rounded-[16px] border border-slate-200 bg-slate-50/70 p-2.5 dark:border-white/10 dark:bg-white/[0.04]">
|
||||
<div className="mb-1.5 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-1.5 text-[12px] font-semibold text-slate-600 dark:text-white/62">
|
||||
<SlidersHorizontal className="h-3.5 w-3.5" />
|
||||
目标
|
||||
</div>
|
||||
<span className="rounded-full border border-slate-200 bg-white px-2 py-0.5 text-[11px] text-slate-500 dark:border-white/10 dark:bg-white/8 dark:text-white/58">
|
||||
<span className="rounded-full border border-slate-200 bg-white px-2 py-0.5 text-[10px] text-slate-500 dark:border-white/10 dark:bg-white/8 dark:text-white/58">
|
||||
{getAiWashStatusLabel(aiWashQuickStatus)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{aiWashQuickPresets.map((preset) => {
|
||||
const isActive = selectedAiWashQuickPreset?.id === preset.id;
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
"w-full rounded-[16px] border px-3 py-2.5 text-left transition",
|
||||
isActive
|
||||
? "border-slate-950 bg-slate-950 text-white shadow-[0_10px_22px_rgba(15,23,42,0.16)] dark:border-white dark:bg-white dark:text-slate-950"
|
||||
: "border-slate-200 bg-white text-slate-600 hover:border-slate-950 hover:text-slate-950 dark:border-white/10 dark:bg-white/7 dark:text-white/64 dark:hover:border-white dark:hover:text-white"
|
||||
)}
|
||||
key={preset.id}
|
||||
onClick={() => {
|
||||
setAiWashQuickPresetId(preset.id);
|
||||
setAiWashQuickStatus("idle");
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<div className="truncate font-semibold text-[13px]">
|
||||
{preset.title}
|
||||
</div>
|
||||
{preset.description ? (
|
||||
<div
|
||||
className={cn(
|
||||
"mt-1 line-clamp-2 text-[12px] leading-5",
|
||||
isActive ? "text-white/72 dark:text-slate-700" : "text-slate-500 dark:text-white/46"
|
||||
)}
|
||||
>
|
||||
{preset.description}
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
setAiWashQuickPresetId(value);
|
||||
setAiWashQuickStatus("idle");
|
||||
}}
|
||||
value={selectedAiWashQuickPreset?.id || aiWashQuickPresetId}
|
||||
>
|
||||
<SelectTrigger className="h-9 rounded-[13px] border-slate-200 bg-white px-2.5 text-[13px] shadow-[0_1px_2px_rgba(15,23,42,0.04)] dark:border-white/10 dark:bg-white/8">
|
||||
<SelectValue placeholder="选择渲染目标" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="border-slate-200 bg-white text-slate-950 dark:border-white/10 dark:bg-slate-950 dark:text-white">
|
||||
{aiWashQuickPresets.map((preset) => (
|
||||
<SelectItem key={preset.id} value={preset.id}>
|
||||
{preset.title}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{selectedAiWashQuickPreset?.description ? (
|
||||
<div className="mt-2 line-clamp-2 text-[11px] leading-4 text-slate-500 dark:text-white/46">
|
||||
{selectedAiWashQuickPreset.description}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="rounded-[22px] border border-slate-200 bg-white p-3 dark:border-white/10 dark:bg-white/[0.04]">
|
||||
<div className="mb-2 text-[14px] font-semibold">参数设置</div>
|
||||
<div className="rounded-[16px] border border-slate-200 bg-white p-2.5 dark:border-white/10 dark:bg-white/[0.04]">
|
||||
<div className="mb-2 text-[12px] font-semibold text-slate-600 dark:text-white/62">
|
||||
参数
|
||||
</div>
|
||||
{shouldShowAiWashQuickModelSelector ? (
|
||||
<div className="mb-3 space-y-1.5">
|
||||
<div className="mb-2.5 space-y-1.5">
|
||||
<Label className="text-[12px] text-slate-500 dark:text-white/54">
|
||||
生图模型
|
||||
</Label>
|
||||
@ -8761,7 +8743,7 @@ export function ImageNode({
|
||||
onValueChange={setAiWashQuickModelId}
|
||||
value={selectedAiWashQuickModel?.id || aiWashQuickModelId}
|
||||
>
|
||||
<SelectTrigger className="h-10 rounded-[15px] border-slate-200 bg-white text-[13px] shadow-[0_1px_2px_rgba(15,23,42,0.04)] dark:border-white/10 dark:bg-white/8">
|
||||
<SelectTrigger className="h-9 rounded-[13px] border-slate-200 bg-white px-2.5 text-[13px] shadow-[0_1px_2px_rgba(15,23,42,0.04)] dark:border-white/10 dark:bg-white/8">
|
||||
<SelectValue placeholder="选择生图模型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="border-slate-200 bg-white text-slate-950 dark:border-white/10 dark:bg-slate-950 dark:text-white">
|
||||
@ -8787,7 +8769,7 @@ export function ImageNode({
|
||||
</Select>
|
||||
<div className="text-[12px] leading-5 text-slate-500 dark:text-white/50">
|
||||
{selectedAiWashQuickModel
|
||||
? `当前模型:${selectedAiWashQuickModel.displayName} · ${
|
||||
? `${selectedAiWashQuickModel.displayName} · ${
|
||||
selectedAiWashQuickModel.unitCost > 0
|
||||
? `${selectedAiWashQuickModel.unitCost} 积分/张`
|
||||
: "未设置积分"
|
||||
@ -8798,15 +8780,15 @@ export function ImageNode({
|
||||
</div>
|
||||
</div>
|
||||
) : selectedAiWashQuickModel ? (
|
||||
<div className="mb-3 rounded-[15px] border border-slate-200 bg-slate-50 px-3 py-2 text-[12px] text-slate-500 dark:border-white/10 dark:bg-white/6 dark:text-white/52">
|
||||
管理员已锁定模型:{selectedAiWashQuickModel.displayName}
|
||||
<div className="mb-2.5 rounded-[13px] border border-slate-200 bg-slate-50 px-2.5 py-2 text-[12px] text-slate-500 dark:border-white/10 dark:bg-white/6 dark:text-white/52">
|
||||
{selectedAiWashQuickModel.displayName}
|
||||
{selectedAiWashQuickModel.unitCost > 0
|
||||
? ` · ${selectedAiWashQuickModel.unitCost} 积分/张`
|
||||
: ""}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="space-y-2.5">
|
||||
<div className="space-y-2">
|
||||
{aiWashQuickControls.map((control) => {
|
||||
const selectedIndex =
|
||||
aiWashQuickLevels[control.id] ?? control.defaultLevel;
|
||||
@ -8816,14 +8798,14 @@ export function ImageNode({
|
||||
null;
|
||||
return (
|
||||
<div
|
||||
className="rounded-[16px] border border-slate-200 bg-slate-50/70 px-3 py-2.5 dark:border-white/10 dark:bg-white/[0.04]"
|
||||
className="rounded-[13px] border border-slate-200 bg-slate-50/70 px-2.5 py-2 dark:border-white/10 dark:bg-white/[0.04]"
|
||||
key={control.id}
|
||||
>
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<div className="text-[13px] font-semibold">
|
||||
<div className="mb-1.5 flex items-center justify-between gap-2">
|
||||
<div className="text-[12px] font-semibold">
|
||||
{control.label}
|
||||
</div>
|
||||
<span className="min-w-9 rounded-full border border-slate-200 bg-white px-2 py-0.5 text-center text-[11px] text-slate-600 dark:border-white/10 dark:bg-white/8 dark:text-white/64">
|
||||
<span className="min-w-8 rounded-full border border-slate-200 bg-white px-1.5 py-0.5 text-center text-[10px] text-slate-600 dark:border-white/10 dark:bg-white/8 dark:text-white/64">
|
||||
{activeLevel?.label || selectedIndex + 1}
|
||||
</span>
|
||||
</div>
|
||||
@ -8839,8 +8821,8 @@ export function ImageNode({
|
||||
}
|
||||
rangeClassName="bg-slate-950 dark:bg-white"
|
||||
step={1}
|
||||
thumbClassName="h-3.5 w-3.5 border border-slate-300 bg-white shadow-[0_0_0_3px_rgba(15,23,42,0.06)]"
|
||||
trackClassName="h-1.5 bg-slate-200 dark:bg-white/14"
|
||||
thumbClassName="h-3 w-3 border border-slate-300 bg-white shadow-[0_0_0_3px_rgba(15,23,42,0.06)]"
|
||||
trackClassName="h-1 bg-slate-200 dark:bg-white/14"
|
||||
value={[selectedIndex]}
|
||||
/>
|
||||
</div>
|
||||
@ -8849,14 +8831,8 @@ export function ImageNode({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{aiWashQuickPrompt ? (
|
||||
<div className="max-h-20 overflow-hidden rounded-[18px] border border-slate-200 bg-slate-50 px-3 py-2 text-[12px] leading-5 text-slate-500 dark:border-white/10 dark:bg-white/[0.04] dark:text-white/48">
|
||||
{aiWashQuickPrompt}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
className="h-11 w-full rounded-[18px] bg-slate-950 text-[14px] font-semibold text-white shadow-[0_12px_26px_rgba(15,23,42,0.16)] hover:bg-slate-900 hover:text-white disabled:bg-slate-200 disabled:text-slate-400 disabled:shadow-none dark:bg-white dark:text-slate-950 dark:hover:bg-slate-100 dark:disabled:bg-white/12 dark:disabled:text-white/30"
|
||||
className="h-9 w-full rounded-[14px] bg-slate-950 text-[13px] font-semibold text-white shadow-[0_10px_22px_rgba(15,23,42,0.14)] hover:bg-slate-900 hover:text-white disabled:bg-slate-200 disabled:text-slate-400 disabled:shadow-none dark:bg-white dark:text-slate-950 dark:hover:bg-slate-100 dark:disabled:bg-white/12 dark:disabled:text-white/30"
|
||||
disabled={
|
||||
isSubmittingAiWashQuick ||
|
||||
!selectedAiWashQuickPreset ||
|
||||
@ -8866,13 +8842,13 @@ export function ImageNode({
|
||||
type="button"
|
||||
>
|
||||
{isSubmittingAiWashQuick ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" />
|
||||
) : (
|
||||
<Sparkles className="mr-2 h-4 w-4" />
|
||||
<Sparkles className="mr-2 h-3.5 w-3.5" />
|
||||
)}
|
||||
{isSubmittingAiWashQuick
|
||||
? "正在 AI洗图"
|
||||
: `开始 ${aiWashQuickModeLabel}${
|
||||
? "生成中"
|
||||
: `${aiWashQuickModeLabel}${
|
||||
selectedAiWashQuickModel?.unitCost
|
||||
? ` · ${selectedAiWashQuickModel.unitCost} 积分`
|
||||
: ""
|
||||
@ -8898,7 +8874,7 @@ export function ImageNode({
|
||||
createPortal(
|
||||
<TooltipProvider delayDuration={200}>
|
||||
<div
|
||||
className="fixed z-[9999] flex items-center rounded-[24px] border border-slate-200 bg-white px-2 py-2 text-gray-800 shadow-[0_14px_38px_rgba(15,23,42,0.16)] dark:border-slate-700 dark:bg-slate-900 dark:text-gray-100"
|
||||
className="fixed z-[9999] flex items-center gap-0.5 rounded-[24px] border border-slate-200 bg-white px-2 py-1.5 text-gray-800 shadow-[0_14px_38px_rgba(15,23,42,0.16)] dark:border-slate-700 dark:bg-slate-900 dark:text-gray-100"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
@ -8913,7 +8889,7 @@ export function ImageNode({
|
||||
180,
|
||||
Math.floor(toolbarPosition.maxWidth / FLOATING_TOOLBAR_SCALE)
|
||||
)}px`,
|
||||
overflowX: "auto",
|
||||
overflowX: "visible",
|
||||
scrollbarWidth: "none",
|
||||
}}
|
||||
>
|
||||
@ -8939,7 +8915,9 @@ export function ImageNode({
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageAddToChatEnabled && hasSelectedImagePrimaryToolbarActions ? (
|
||||
{imageAddToChatEnabled &&
|
||||
(hasSelectedImagePrimaryToolbarActions ||
|
||||
hasSelectedImageSecondaryToolbarActions) ? (
|
||||
<div className="mx-1 h-6 w-px bg-slate-200 dark:bg-slate-700" />
|
||||
) : null}
|
||||
|
||||
@ -8994,40 +8972,6 @@ export function ImageNode({
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageBoxCutoutEnabled ? (
|
||||
<Button
|
||||
className="h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
|
||||
disabled={isProcessing}
|
||||
onClick={() => handleAction("box-cutout")}
|
||||
title={imageBoxCutoutItem.label}
|
||||
variant="ghost"
|
||||
>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-1.5 h-4 w-4"
|
||||
fallback={Scan}
|
||||
item={imageBoxCutoutItem}
|
||||
/>
|
||||
{imageBoxCutoutItem.label}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageLocalEditEnabled ? (
|
||||
<Button
|
||||
className="h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
|
||||
disabled={isProcessing || isSubmittingLocalEdit}
|
||||
onClick={() => handleAction("local-edit")}
|
||||
title={imageLocalEditItem.label}
|
||||
variant="ghost"
|
||||
>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-1.5 h-4 w-4"
|
||||
fallback={Wand2}
|
||||
item={imageLocalEditItem}
|
||||
/>
|
||||
{imageLocalEditItem.label}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageAiWashQuickEnabled ? (
|
||||
<Button
|
||||
className={cn(
|
||||
@ -9055,40 +8999,6 @@ export function ImageNode({
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageRemoveWatermarkEnabled ? (
|
||||
<Button
|
||||
className="h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
|
||||
disabled={isProcessing}
|
||||
onClick={() => handleAction("watermark")}
|
||||
title={imageRemoveWatermarkItem.label}
|
||||
variant="ghost"
|
||||
>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-1.5 h-4 w-4"
|
||||
fallback={Eraser}
|
||||
item={imageRemoveWatermarkItem}
|
||||
/>
|
||||
{imageRemoveWatermarkItem.label}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageResizeEnabled ? (
|
||||
<Button
|
||||
className="h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
|
||||
disabled={isProcessing}
|
||||
onClick={() => handleAction("resize")}
|
||||
title={imageResizeItem.label}
|
||||
variant="ghost"
|
||||
>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-1.5 h-4 w-4"
|
||||
fallback={Maximize2}
|
||||
item={imageResizeItem}
|
||||
/>
|
||||
{imageResizeItem.label}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageTextEditEnabled ? (
|
||||
<Button
|
||||
className="h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
|
||||
@ -9106,74 +9016,6 @@ export function ImageNode({
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageMultiAngleEnabled ? (
|
||||
<Button
|
||||
className="h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
|
||||
disabled={isProcessing}
|
||||
onClick={() => handleAction("angle")}
|
||||
title={imageMultiAngleItem.label}
|
||||
variant="ghost"
|
||||
>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-1.5 h-4 w-4"
|
||||
fallback={Box}
|
||||
item={imageMultiAngleItem}
|
||||
/>
|
||||
{imageMultiAngleItem.label}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageGenerate3dModelEnabled ? (
|
||||
<Button
|
||||
className="h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
|
||||
disabled={isProcessing}
|
||||
onClick={() => handleAction("generate-3d-model")}
|
||||
title={imageGenerate3dModelItem.label}
|
||||
variant="ghost"
|
||||
>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-1.5 h-4 w-4"
|
||||
fallback={Box}
|
||||
item={imageGenerate3dModelItem}
|
||||
/>
|
||||
{imageGenerate3dModelItem.label}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageGenerate3dWorldEnabled ? (
|
||||
<Button
|
||||
className="h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
|
||||
disabled={isProcessing}
|
||||
onClick={() => handleAction("generate-3d-world")}
|
||||
title={imageGenerate3dWorldItem.label}
|
||||
variant="ghost"
|
||||
>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-1.5 h-4 w-4"
|
||||
fallback={Globe2}
|
||||
item={imageGenerate3dWorldItem}
|
||||
/>
|
||||
{imageGenerate3dWorldItem.label}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{imageLayerSplitEnabled ? (
|
||||
<Button
|
||||
className="h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
|
||||
disabled={isProcessing}
|
||||
onClick={() => handleAction("layer-split")}
|
||||
title={imageLayerSplitItem.label}
|
||||
variant="ghost"
|
||||
>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-1.5 h-4 w-4"
|
||||
fallback={Layers}
|
||||
item={imageLayerSplitItem}
|
||||
/>
|
||||
{imageLayerSplitItem.label}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{hasSelectedImagePrimaryToolbarActions &&
|
||||
hasSelectedImageSecondaryToolbarActions ? (
|
||||
<div className="mx-1 h-6 w-px bg-slate-200 dark:bg-slate-700" />
|
||||
@ -9201,6 +9043,90 @@ export function ImageNode({
|
||||
className="bg-white opacity-100 backdrop-blur-none dark:bg-slate-900 dark:text-slate-100"
|
||||
side="bottom"
|
||||
>
|
||||
{imageBoxCutoutEnabled ? (
|
||||
<DropdownMenuItem onClick={() => handleAction("box-cutout")}>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-2 h-4 w-4"
|
||||
fallback={Scan}
|
||||
item={imageBoxCutoutItem}
|
||||
/>
|
||||
{imageBoxCutoutItem.label}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
{imageLocalEditEnabled ? (
|
||||
<DropdownMenuItem onClick={() => handleAction("local-edit")}>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-2 h-4 w-4"
|
||||
fallback={Wand2}
|
||||
item={imageLocalEditItem}
|
||||
/>
|
||||
{imageLocalEditItem.label}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
{imageRemoveWatermarkEnabled ? (
|
||||
<DropdownMenuItem onClick={() => handleAction("watermark")}>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-2 h-4 w-4"
|
||||
fallback={Eraser}
|
||||
item={imageRemoveWatermarkItem}
|
||||
/>
|
||||
{imageRemoveWatermarkItem.label}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
{imageResizeEnabled ? (
|
||||
<DropdownMenuItem onClick={() => handleAction("resize")}>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-2 h-4 w-4"
|
||||
fallback={Maximize2}
|
||||
item={imageResizeItem}
|
||||
/>
|
||||
{imageResizeItem.label}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
{imageMultiAngleEnabled ? (
|
||||
<DropdownMenuItem onClick={() => handleAction("angle")}>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-2 h-4 w-4"
|
||||
fallback={Box}
|
||||
item={imageMultiAngleItem}
|
||||
/>
|
||||
{imageMultiAngleItem.label}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
{imageGenerate3dModelEnabled ? (
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleAction("generate-3d-model")}
|
||||
>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-2 h-4 w-4"
|
||||
fallback={Box}
|
||||
item={imageGenerate3dModelItem}
|
||||
/>
|
||||
{imageGenerate3dModelItem.label}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
{imageGenerate3dWorldEnabled ? (
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleAction("generate-3d-world")}
|
||||
>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-2 h-4 w-4"
|
||||
fallback={Globe2}
|
||||
item={imageGenerate3dWorldItem}
|
||||
/>
|
||||
{imageGenerate3dWorldItem.label}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
{imageLayerSplitEnabled ? (
|
||||
<DropdownMenuItem onClick={() => handleAction("layer-split")}>
|
||||
<CanvasToolbarIcon
|
||||
className="mr-2 h-4 w-4"
|
||||
fallback={Layers}
|
||||
item={imageLayerSplitItem}
|
||||
/>
|
||||
{imageLayerSplitItem.label}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
{imageExtendEnabled ? (
|
||||
<DropdownMenuItem onClick={() => handleAction("extend")}>
|
||||
<CanvasToolbarIcon
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user