Replica LibTV image toolbar menus
This commit is contained in:
parent
4acf95490d
commit
df9a28d752
@ -3730,24 +3730,60 @@ function SceneToolbarPopover({
|
||||
onSelect?: (option?: string) => void;
|
||||
}) {
|
||||
const [lightingPrompt, setLightingPrompt] = useState("");
|
||||
const quickOptions: Record<Exclude<SceneToolbarMenu, "lighting" | null>, string[]> = {
|
||||
crop: ["4 宫格切分", "9 宫格切分", "16 宫格切分", "25 宫格切分"],
|
||||
grid: ["2 x 2", "3 x 3", "4 x 4", "自定义宫格"],
|
||||
hd: ["2倍高清", "4倍高清"],
|
||||
const [customGridOpen, setCustomGridOpen] = useState(false);
|
||||
const popoverOffset: Record<Exclude<SceneToolbarMenu, null>, number> = {
|
||||
crop: 170,
|
||||
grid: -38,
|
||||
hd: 70,
|
||||
lighting: -145,
|
||||
orbit: -245,
|
||||
panorama: -360,
|
||||
};
|
||||
const officialPopoverStyle = {
|
||||
left: anchor ? anchor.centerX + popoverOffset[menu] : `calc(50% + ${popoverOffset[menu]}px)`,
|
||||
top: anchor ? anchor.topY - (24 * anchor.zoom + 10) + 8 : 168,
|
||||
};
|
||||
const popoverSurfaceStyle = {
|
||||
background: "rgba(31, 31, 31, 0.92)",
|
||||
border: "0.5px solid #363636",
|
||||
borderRadius: 12,
|
||||
boxShadow: "0 4px 10px rgba(0, 0, 0, 0.2), 0 2px 4px rgba(0, 0, 0, 0.12)",
|
||||
WebkitBackdropFilter: "blur(16px)",
|
||||
backdropFilter: "blur(16px)",
|
||||
};
|
||||
const quickOptions: Record<"orbit" | "panorama", string[]> = {
|
||||
orbit: ["正侧背三视角", "八方向参考", "环绕镜头"],
|
||||
panorama: ["全景", "半身", "近景"],
|
||||
};
|
||||
const nineGridOptions = [
|
||||
"多机位九宫格",
|
||||
"剧情推演四宫格",
|
||||
"25宫格连贯分镜",
|
||||
"电影级光影校正",
|
||||
"角色三视图生成",
|
||||
"画面推演 - 3秒后",
|
||||
"画面推演 - 5秒前",
|
||||
];
|
||||
const hdToolOptions: Array<{ key: string; label: string; active?: boolean }> = [
|
||||
{ key: "upscale", label: "高清", active: true },
|
||||
{ key: "outpaint", label: "扩图" },
|
||||
{ key: "repaint", label: "重绘" },
|
||||
{ key: "erase", label: "擦除" },
|
||||
{ key: "removeBg", label: "抠图" },
|
||||
{ key: "crop", label: "裁剪" },
|
||||
];
|
||||
const cropOptions = [
|
||||
{ type: 4, label: "4宫格 (2×2)" },
|
||||
{ type: 9, label: "9宫格 (3×3)" },
|
||||
{ type: 16, label: "16宫格 (4×4)" },
|
||||
{ type: 25, label: "25宫格 (5×5)" },
|
||||
];
|
||||
|
||||
if (menu === "lighting") {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-auto absolute z-40 w-[420px] rounded-[14px] border border-white/10 bg-[#262626]/98 p-4 text-white shadow-[0_24px_80px_rgba(0,0,0,0.46)] backdrop-blur-[10px]",
|
||||
anchor
|
||||
? "origin-top -translate-x-1/2 scale-[0.7]"
|
||||
: "left-1/2 top-[214px] origin-top -translate-x-1/2 scale-[0.7]"
|
||||
)}
|
||||
style={anchor ? { left: anchor.centerX, top: anchor.topY + 8 } : undefined}
|
||||
className="pointer-events-auto absolute z-40 w-[420px] -translate-x-1/2 p-4 text-white"
|
||||
style={{ ...officialPopoverStyle, ...popoverSurfaceStyle }}
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div className="font-semibold text-[15px] text-white/86">打光</div>
|
||||
@ -3770,19 +3806,117 @@ function SceneToolbarPopover({
|
||||
);
|
||||
}
|
||||
|
||||
if (menu === "grid") {
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-auto absolute z-40 flex w-[180px] -translate-x-1/2 flex-col p-1.5 font-sans"
|
||||
role="menu"
|
||||
style={{ ...officialPopoverStyle, ...popoverSurfaceStyle }}
|
||||
>
|
||||
{nineGridOptions.map((option) => (
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center gap-3 rounded-[8px] px-2 py-2.5 text-left transition-colors duration-200 hover:bg-white/8 active:bg-white/12"
|
||||
key={option}
|
||||
onClick={() => onSelect?.(option)}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<NineGridPresetGlyph />
|
||||
<span className="min-w-0 flex-1 truncate text-[14px] font-normal leading-snug text-white/86">
|
||||
{option}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (menu === "hd") {
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-auto absolute z-40 flex w-[120px] -translate-x-1/2 flex-col gap-1 p-1 font-sans"
|
||||
role="menu"
|
||||
style={{ ...officialPopoverStyle, ...popoverSurfaceStyle }}
|
||||
>
|
||||
{hdToolOptions.map((option) => (
|
||||
<button
|
||||
className={cn(
|
||||
"flex h-8 w-full shrink-0 items-center gap-2 rounded-[8px] px-2 text-left text-[13px] font-normal leading-snug text-white/86 transition-colors",
|
||||
option.active ? "bg-white/16" : "hover:bg-white/8 active:bg-white/12"
|
||||
)}
|
||||
key={option.key}
|
||||
onClick={() => onSelect?.(option.label)}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<ImageToolMenuGlyph tool={option.key} />
|
||||
<span className="min-w-0 flex-1">{option.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (menu === "crop") {
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-auto absolute z-40 flex w-[150px] -translate-x-1/2 flex-col p-1.5 font-sans"
|
||||
role="menu"
|
||||
style={{ ...officialPopoverStyle, ...popoverSurfaceStyle }}
|
||||
>
|
||||
{cropOptions.map((option) => (
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center rounded-[8px] px-3 py-2.5 text-left transition-colors duration-200 hover:bg-white/8 active:bg-white/12"
|
||||
key={option.type}
|
||||
onClick={() => onSelect?.(option.label)}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<span className="min-w-0 truncate text-[14px] font-normal leading-snug text-white/86">
|
||||
{option.label}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
<div className="mx-2 my-0.5 h-px bg-white/12" />
|
||||
<div onMouseEnter={() => setCustomGridOpen(true)} onMouseLeave={() => setCustomGridOpen(false)}>
|
||||
<button
|
||||
className={cn(
|
||||
"flex w-full cursor-pointer items-center justify-between rounded-[8px] px-3 py-2.5 text-left transition-colors duration-200 hover:bg-white/8 active:bg-white/12",
|
||||
customGridOpen && "bg-white/8"
|
||||
)}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<span className="min-w-0 truncate text-[14px] font-normal leading-snug text-white/86">自定义</span>
|
||||
<ChevronDown className="h-3 w-3 shrink-0 -rotate-90 text-white/60" />
|
||||
</button>
|
||||
{customGridOpen ? (
|
||||
<div
|
||||
className="absolute left-full top-0 ml-1.5 p-1.5"
|
||||
onMouseEnter={() => setCustomGridOpen(true)}
|
||||
onMouseLeave={() => setCustomGridOpen(false)}
|
||||
style={popoverSurfaceStyle}
|
||||
>
|
||||
<CustomGridPicker onSelect={(rows, columns) => onSelect?.(`${rows} x ${columns}`)} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-auto absolute z-40 w-[260px] rounded-[14px] border border-white/10 bg-[#262626]/98 p-2 text-white shadow-[0_24px_80px_rgba(0,0,0,0.46)] backdrop-blur-[10px]",
|
||||
anchor
|
||||
? "origin-top -translate-x-1/2 scale-[0.7]"
|
||||
: "left-1/2 top-[214px] origin-top -translate-x-1/2 scale-[0.7]"
|
||||
)}
|
||||
style={anchor ? { left: anchor.centerX, top: anchor.topY + 8 } : undefined}
|
||||
className="pointer-events-auto absolute z-40 w-[180px] -translate-x-1/2 p-1.5 text-white"
|
||||
style={{ ...officialPopoverStyle, ...popoverSurfaceStyle }}
|
||||
>
|
||||
{quickOptions[menu].map((option) => (
|
||||
<button
|
||||
className="flex h-10 w-full items-center justify-between rounded-[10px] px-3 text-left text-[13px] text-white/76 hover:bg-white/8 hover:text-white"
|
||||
className="flex h-9 w-full items-center justify-between rounded-[8px] px-3 text-left text-[13px] text-white/76 hover:bg-white/8 hover:text-white"
|
||||
key={option}
|
||||
onClick={() => onSelect?.(option)}
|
||||
type="button"
|
||||
@ -3795,6 +3929,100 @@ function SceneToolbarPopover({
|
||||
);
|
||||
}
|
||||
|
||||
function NineGridPresetGlyph() {
|
||||
return (
|
||||
<svg aria-hidden="true" className="h-5 w-5 shrink-0 text-white/46" fill="none" viewBox="0 0 20 20">
|
||||
<path d="M3 7.2 10 3.4l7 3.8-7 3.8-7-3.8Z" stroke="currentColor" strokeLinejoin="round" strokeWidth="1.5" />
|
||||
<path d="m3 10.2 7 3.8 7-3.8M3 13.2l7 3.8 7-3.8" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ImageToolMenuGlyph({ tool }: { tool: string }) {
|
||||
if (tool === "upscale") {
|
||||
return (
|
||||
<span className="flex h-[14px] min-w-[21px] items-center justify-center rounded-[3px] border border-white/34 text-[9px] font-bold leading-none text-white/46">
|
||||
HD
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (tool === "outpaint") {
|
||||
return (
|
||||
<svg aria-hidden="true" className="h-[14px] w-[14px] shrink-0 text-white/46" fill="none" viewBox="0 0 14 14">
|
||||
<path d="M1.8 5V1.8H5M9 1.8h3.2V5M12.2 9v3.2H9M5 12.2H1.8V9" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.4" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
if (tool === "repaint") {
|
||||
return (
|
||||
<svg aria-hidden="true" className="h-[14px] w-[14px] shrink-0 text-white/46" fill="none" viewBox="0 0 14 14">
|
||||
<path d="M3 10.8 10.8 3l1.2 1.2-7.8 7.8H3v-1.2Z" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
if (tool === "erase") {
|
||||
return (
|
||||
<svg aria-hidden="true" className="h-[14px] w-[14px] shrink-0 text-white/46" fill="none" viewBox="0 0 14 14">
|
||||
<path d="m2.5 8.5 5.8-5.8 3 3-5.8 5.8H3.2L2.5 8.5Z" stroke="currentColor" strokeLinejoin="round" strokeWidth="1.3" />
|
||||
<path d="M6.2 11.5h5" stroke="currentColor" strokeLinecap="round" strokeWidth="1.3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
if (tool === "removeBg") {
|
||||
return (
|
||||
<svg aria-hidden="true" className="h-[14px] w-[14px] shrink-0 text-white/46" fill="none" viewBox="0 0 14 14">
|
||||
<path d="M2 2h10v10H2V2Z" stroke="currentColor" strokeWidth="1.2" />
|
||||
<path d="M2 12 12 2M2 8l6-6M6 12l6-6" stroke="currentColor" strokeWidth="1.1" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<svg aria-hidden="true" className="h-[14px] w-[14px] shrink-0 text-white/46" fill="none" viewBox="0 0 14 14">
|
||||
<path d="M4 1.8v8.2h8.2M1.8 4h8.2v8.2" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomGridPicker({ onSelect }: { onSelect: (rows: number, columns: number) => void }) {
|
||||
const [hovered, setHovered] = useState({ columns: 0, rows: 0 });
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2.5 p-2">
|
||||
<div className="flex items-center justify-between px-0.5">
|
||||
<span className="text-[13px] text-white/52">自定义宫格</span>
|
||||
<span className="text-[13px] font-medium text-white/86">
|
||||
{hovered.rows > 0 && hovered.columns > 0 ? `${hovered.columns} x ${hovered.rows}` : ""}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-1" onMouseLeave={() => setHovered({ columns: 0, rows: 0 })}>
|
||||
{Array.from({ length: 25 }).map((_, index) => {
|
||||
const rows = Math.floor(index / 5) + 1;
|
||||
const columns = (index % 5) + 1;
|
||||
const active = rows <= hovered.rows && columns <= hovered.columns;
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
"h-8 w-8 rounded border transition-colors duration-75",
|
||||
active ? "border-blue-400/60 bg-blue-500/40" : "border-white/12 bg-white/8 hover:bg-white/12"
|
||||
)}
|
||||
key={index}
|
||||
onClick={() => onSelect(rows, columns)}
|
||||
onMouseEnter={() => setHovered({ columns, rows })}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
type="button"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SceneToolIcon({ kind }: { kind: "panorama" | "orbit" | "lighting" | "grid" | "hd" | "crop" }) {
|
||||
if (kind === "panorama") {
|
||||
return <span className="font-['Barlow_Condensed',sans-serif] text-[16px] font-bold leading-none text-white/86">720</span>;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user