fix: refine angle panel proportions
This commit is contained in:
parent
b82bace466
commit
e157480ec2
@ -238,6 +238,17 @@ const FLOATING_TOOLBAR_SCALE = 0.8;
|
||||
const IMAGE_EDIT_PANEL_SCALE = 0.5;
|
||||
const ANGLE_PREVIEW_HORIZONTAL_DRAG_MULTIPLIER = 0.72;
|
||||
const ANGLE_PREVIEW_VERTICAL_DRAG_MULTIPLIER = 0.46;
|
||||
const ANGLE_PANEL_SOURCE_WIDTH = 520;
|
||||
const ANGLE_PANEL_SOURCE_HEIGHT = 700;
|
||||
const ANGLE_PANEL_PREVIEW_SIZE = 392;
|
||||
const ANGLE_PANEL_CAMERA_SCENE_SIZE = 384;
|
||||
const ANGLE_PANEL_WIREFRAME_RADIUS = 132;
|
||||
const ANGLE_PANEL_WIREFRAME_CENTER = 160;
|
||||
const ANGLE_PREVIEW_ORBIT_ROTATIONS = [
|
||||
0, 14, 28, 42, 56, 70, 84, 98, 112, 126, 140, 154, 168,
|
||||
];
|
||||
const ANGLE_PREVIEW_LATITUDE_RATIOS = [1, 0.9, 0.78, 0.64, 0.5];
|
||||
const ANGLE_PREVIEW_MERIDIAN_RATIOS = [1, 0.9, 0.78, 0.64];
|
||||
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>"
|
||||
@ -469,12 +480,12 @@ function AngleEditPanelSlider({
|
||||
valueLabel: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<span className="text-[18px] font-semibold tracking-[0] text-[#26272b]">
|
||||
<span className="text-[17px] font-semibold tracking-[0] text-[#26272b]">
|
||||
{label}
|
||||
</span>
|
||||
<span className="text-[17px] font-medium tabular-nums text-[#7d7f86]">
|
||||
<span className="text-[16px] font-medium tabular-nums text-[#7d7f86]">
|
||||
{valueLabel}
|
||||
</span>
|
||||
</div>
|
||||
@ -485,7 +496,7 @@ function AngleEditPanelSlider({
|
||||
onValueChange={([nextValue]) => onChange(nextValue ?? value)}
|
||||
rangeClassName="bg-[#2d3035]"
|
||||
step={step}
|
||||
thumbClassName="h-[22px] w-[22px] border-[5px] border-[#2d3035] bg-white shadow-[0_2px_10px_rgba(24,24,27,0.14)]"
|
||||
thumbClassName="h-5 w-5 border-[4px] border-[#2d3035] bg-white shadow-[0_2px_10px_rgba(24,24,27,0.14)]"
|
||||
trackClassName="h-[4px] bg-[#e7e8ec]"
|
||||
value={[value]}
|
||||
/>
|
||||
@ -2069,10 +2080,10 @@ export function ImageNode({
|
||||
fitSizeWithinBox({
|
||||
sourceWidth: angleSourceResolution.width,
|
||||
sourceHeight: angleSourceResolution.height,
|
||||
maxWidth: anglePanelMode === "subject" ? 178 : 136,
|
||||
maxHeight: anglePanelMode === "subject" ? 150 : 112,
|
||||
minWidth: 74,
|
||||
minHeight: 62,
|
||||
maxWidth: anglePanelMode === "subject" ? 236 : 188,
|
||||
maxHeight: anglePanelMode === "subject" ? 196 : 150,
|
||||
minWidth: 96,
|
||||
minHeight: 80,
|
||||
}),
|
||||
[anglePanelMode, angleSourceResolution.height, angleSourceResolution.width]
|
||||
);
|
||||
@ -2103,9 +2114,9 @@ export function ImageNode({
|
||||
const angleCameraOrbitPosition = useMemo(() => {
|
||||
const horizontalRadians = (angleHorizontalAngle * Math.PI) / 180;
|
||||
const normalizedVertical = (angleVerticalAngle + 30) / 90;
|
||||
const x = Math.sin(horizontalRadians) * 92;
|
||||
const y = -normalizedVertical * 60 + 24;
|
||||
const z = Math.cos(horizontalRadians) * 28;
|
||||
const x = Math.sin(horizontalRadians) * 110;
|
||||
const y = -normalizedVertical * 72 + 26;
|
||||
const z = Math.cos(horizontalRadians) * 38;
|
||||
return {
|
||||
x: Number(x.toFixed(2)),
|
||||
y: Number(y.toFixed(2)),
|
||||
@ -2127,8 +2138,18 @@ export function ImageNode({
|
||||
length: Number(length.toFixed(2)),
|
||||
};
|
||||
}, [angleCameraOrbitPosition]);
|
||||
const angleOrbitWireframeStyle = useMemo<React.CSSProperties>(
|
||||
() => ({
|
||||
transform: `rotateX(${-angleVerticalAngle * 0.22}deg) rotateY(${angleHorizontalAngle * 0.28}deg) rotateZ(${angleHorizontalAngle * 0.04}deg)`,
|
||||
transformStyle: "preserve-3d",
|
||||
transition: isAnglePreviewDragging
|
||||
? "none"
|
||||
: "transform 180ms cubic-bezier(0.22, 1, 0.36, 1)",
|
||||
}),
|
||||
[angleHorizontalAngle, angleVerticalAngle, isAnglePreviewDragging]
|
||||
);
|
||||
const angleCameraSceneScale = useMemo(
|
||||
() => Number((0.9 + (angleZoom / 100) * 0.24).toFixed(3)),
|
||||
() => Number((0.94 + (angleZoom / 100) * 0.22).toFixed(3)),
|
||||
[angleZoom]
|
||||
);
|
||||
const cropImageMetrics = useMemo(() => {
|
||||
@ -3586,8 +3607,8 @@ export function ImageNode({
|
||||
}
|
||||
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
const panelWidth = 372 * IMAGE_EDIT_PANEL_SCALE;
|
||||
const panelHeight = 760 * IMAGE_EDIT_PANEL_SCALE;
|
||||
const panelWidth = ANGLE_PANEL_SOURCE_WIDTH * IMAGE_EDIT_PANEL_SCALE;
|
||||
const panelHeight = ANGLE_PANEL_SOURCE_HEIGHT * IMAGE_EDIT_PANEL_SCALE;
|
||||
const spacing = 16;
|
||||
const maxTop = Math.max(12, window.innerHeight - panelHeight);
|
||||
|
||||
@ -8191,7 +8212,7 @@ export function ImageNode({
|
||||
typeof document !== "undefined" &&
|
||||
createPortal(
|
||||
<div
|
||||
className="fixed z-[9999] w-[372px] rounded-[28px] border border-[#ececef] bg-white p-5 shadow-[0_24px_64px_rgba(15,23,42,0.12)]"
|
||||
className="fixed z-[9999] w-[520px] rounded-[28px] border border-[#ececef] bg-white p-6 shadow-[0_24px_64px_rgba(15,23,42,0.12)]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
@ -8228,7 +8249,7 @@ export function ImageNode({
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
"h-14 rounded-[16px] text-[16px] font-semibold tracking-[0] transition-all",
|
||||
"h-[52px] rounded-[16px] text-[16px] font-semibold tracking-[0] transition-all",
|
||||
active
|
||||
? "bg-white text-[#1f2023] shadow-[0_1px_2px_rgba(16,24,40,0.06)]"
|
||||
: "text-[#767982] hover:text-[#2d3036]"
|
||||
@ -8245,10 +8266,10 @@ export function ImageNode({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-6 overflow-hidden rounded-[26px] border border-[#efeff2] bg-[#fcfcfd] px-4 py-5">
|
||||
<div className="relative mt-4 overflow-hidden rounded-[26px] border border-[#efeff2] bg-[#fcfcfd] px-4 py-4">
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex h-[410px] items-center justify-center overflow-hidden rounded-[22px] [perspective:1200px]",
|
||||
"relative flex h-[332px] items-center justify-center overflow-hidden rounded-[22px] [perspective:1200px]",
|
||||
isApplyingAngleEdit
|
||||
? "cursor-default"
|
||||
: isAnglePreviewDragging
|
||||
@ -8262,73 +8283,64 @@ export function ImageNode({
|
||||
onPointerUp={handleAnglePreviewPointerEnd}
|
||||
style={{ touchAction: "none" }}
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 flex items-center justify-center"
|
||||
style={angleOrbitWireframeStyle}
|
||||
>
|
||||
<svg
|
||||
className="h-[312px] w-[312px]"
|
||||
className="shrink-0"
|
||||
fill="none"
|
||||
height={ANGLE_PANEL_PREVIEW_SIZE}
|
||||
viewBox="0 0 320 320"
|
||||
width={ANGLE_PANEL_PREVIEW_SIZE}
|
||||
>
|
||||
<ellipse
|
||||
cx="160"
|
||||
cy="160"
|
||||
rx="118"
|
||||
ry="118"
|
||||
stroke="#e6e8ee"
|
||||
strokeWidth="1.2"
|
||||
/>
|
||||
<ellipse
|
||||
cx="160"
|
||||
cy="160"
|
||||
rx="118"
|
||||
ry="118"
|
||||
stroke="#e8eaf0"
|
||||
strokeWidth="1.1"
|
||||
transform="rotate(22 160 160)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="160"
|
||||
cy="160"
|
||||
rx="118"
|
||||
ry="118"
|
||||
stroke="#e8eaf0"
|
||||
strokeWidth="1.1"
|
||||
transform="rotate(44 160 160)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="160"
|
||||
cy="160"
|
||||
rx="118"
|
||||
ry="118"
|
||||
stroke="#e8eaf0"
|
||||
strokeWidth="1.1"
|
||||
transform="rotate(66 160 160)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="160"
|
||||
cy="160"
|
||||
rx="118"
|
||||
ry="94"
|
||||
stroke="#eceef4"
|
||||
strokeWidth="1.05"
|
||||
/>
|
||||
<ellipse
|
||||
cx="160"
|
||||
cy="160"
|
||||
rx="118"
|
||||
ry="72"
|
||||
stroke="#eceef4"
|
||||
strokeWidth="1.05"
|
||||
/>
|
||||
<ellipse
|
||||
cx="160"
|
||||
cy="160"
|
||||
rx="94"
|
||||
ry="118"
|
||||
stroke="#eceef4"
|
||||
strokeDasharray="4 6"
|
||||
strokeWidth="1.05"
|
||||
transform="rotate(90 160 160)"
|
||||
/>
|
||||
{ANGLE_PREVIEW_ORBIT_ROTATIONS.map((rotation, index) => (
|
||||
<ellipse
|
||||
key={`orbit-rotation-${rotation}`}
|
||||
cx={ANGLE_PANEL_WIREFRAME_CENTER}
|
||||
cy={ANGLE_PANEL_WIREFRAME_CENTER}
|
||||
rx={ANGLE_PANEL_WIREFRAME_RADIUS}
|
||||
ry={ANGLE_PANEL_WIREFRAME_RADIUS}
|
||||
stroke={index === 0 ? "#e5e8ef" : "#e8ebf1"}
|
||||
strokeOpacity={index === 0 ? 0.92 : 0.84}
|
||||
strokeWidth={index % 3 === 0 ? "1.12" : "1.02"}
|
||||
transform={
|
||||
rotation === 0
|
||||
? undefined
|
||||
: `rotate(${rotation} ${ANGLE_PANEL_WIREFRAME_CENTER} ${ANGLE_PANEL_WIREFRAME_CENTER})`
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{ANGLE_PREVIEW_LATITUDE_RATIOS.map((ratio, index) => (
|
||||
<ellipse
|
||||
key={`orbit-latitude-${ratio}`}
|
||||
cx={ANGLE_PANEL_WIREFRAME_CENTER}
|
||||
cy={ANGLE_PANEL_WIREFRAME_CENTER}
|
||||
rx={ANGLE_PANEL_WIREFRAME_RADIUS}
|
||||
ry={Number(
|
||||
(ANGLE_PANEL_WIREFRAME_RADIUS * ratio).toFixed(2)
|
||||
)}
|
||||
stroke="#eceff4"
|
||||
strokeOpacity={0.86 - index * 0.08}
|
||||
strokeWidth={index === 0 ? "1.08" : "1.0"}
|
||||
/>
|
||||
))}
|
||||
{ANGLE_PREVIEW_MERIDIAN_RATIOS.map((ratio, index) => (
|
||||
<ellipse
|
||||
key={`orbit-meridian-${ratio}`}
|
||||
cx={ANGLE_PANEL_WIREFRAME_CENTER}
|
||||
cy={ANGLE_PANEL_WIREFRAME_CENTER}
|
||||
rx={Number(
|
||||
(ANGLE_PANEL_WIREFRAME_RADIUS * ratio).toFixed(2)
|
||||
)}
|
||||
ry={ANGLE_PANEL_WIREFRAME_RADIUS}
|
||||
stroke="#e9edf3"
|
||||
strokeOpacity={0.78 - index * 0.08}
|
||||
strokeDasharray={index === 0 ? undefined : "4 6"}
|
||||
strokeWidth="1.0"
|
||||
transform={`rotate(90 ${ANGLE_PANEL_WIREFRAME_CENTER} ${ANGLE_PANEL_WIREFRAME_CENTER})`}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
@ -8387,7 +8399,7 @@ export function ImageNode({
|
||||
style={{
|
||||
width: `${anglePreviewFrameSize.width}px`,
|
||||
height: `${anglePreviewFrameSize.height}px`,
|
||||
transform: `rotateX(${-angleVerticalAngle * 0.46}deg) rotateY(${angleHorizontalAngle * 0.62}deg) scale(${anglePreviewScale})`,
|
||||
transform: `rotateX(${-angleVerticalAngle * 0.5}deg) rotateY(${angleHorizontalAngle * 0.68}deg) scale(${anglePreviewScale})`,
|
||||
transformStyle: "preserve-3d",
|
||||
transition: isAnglePreviewDragging
|
||||
? "none"
|
||||
@ -8397,7 +8409,7 @@ export function ImageNode({
|
||||
<div
|
||||
className="absolute inset-[10%] rounded-full bg-black/12 blur-[20px]"
|
||||
style={{
|
||||
transform: `translate3d(0, ${anglePreviewFrameSize.height * 0.34}px, -18px) scale(${0.9 + angleZoom / 260})`,
|
||||
transform: `translate3d(0, ${anglePreviewFrameSize.height * 0.31}px, -18px) scale(${0.96 + angleZoom / 240})`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
@ -8456,22 +8468,24 @@ export function ImageNode({
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="relative z-[2] h-[320px] w-[320px]"
|
||||
className="relative z-[2]"
|
||||
style={{
|
||||
height: `${ANGLE_PANEL_CAMERA_SCENE_SIZE}px`,
|
||||
transform: `scale(${angleCameraSceneScale})`,
|
||||
transition: isAnglePreviewDragging
|
||||
? "none"
|
||||
: "transform 180ms cubic-bezier(0.22, 1, 0.36, 1)",
|
||||
width: `${ANGLE_PANEL_CAMERA_SCENE_SIZE}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute left-1/2 top-1/2 z-[2] h-[90px] w-[90px] -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-[18px] border border-[#eceff4] bg-white/96 shadow-[0_12px_28px_rgba(15,23,42,0.08)]"
|
||||
className="absolute left-1/2 top-1/2 z-[2] h-[116px] w-[116px] -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-[22px] border border-[#eceff4] bg-white/96 shadow-[0_12px_28px_rgba(15,23,42,0.08)]"
|
||||
style={{
|
||||
backdropFilter: "blur(8px)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-[8px] rounded-[14px] border border-[#eef1f5]"
|
||||
className="absolute inset-[10px] rounded-[17px] border border-[#eef1f5]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(180deg,#fcfcfd,#f5f6f8), linear-gradient(90deg,rgba(227,230,236,0.55) 1px,transparent 1px), linear-gradient(rgba(227,230,236,0.55) 1px,transparent 1px)",
|
||||
@ -8482,7 +8496,7 @@ export function ImageNode({
|
||||
>
|
||||
<img
|
||||
alt={displayTitle}
|
||||
className="absolute left-1/2 top-1/2 max-h-[58px] max-w-[58px] -translate-x-1/2 -translate-y-1/2 object-contain"
|
||||
className="absolute left-1/2 top-1/2 max-h-[76px] max-w-[76px] -translate-x-1/2 -translate-y-1/2 object-contain"
|
||||
draggable={false}
|
||||
src={anglePreviewImageSrc}
|
||||
/>
|
||||
@ -8492,7 +8506,7 @@ export function ImageNode({
|
||||
<div
|
||||
className="absolute left-1/2 top-1/2 z-[1] h-px bg-[#dfe4ec]"
|
||||
style={{
|
||||
width: `${Math.max(32, angleCameraConnection.length - 18)}px`,
|
||||
width: `${Math.max(40, angleCameraConnection.length - 12)}px`,
|
||||
transform: `translateY(-0.5px) rotate(${angleCameraConnection.angle}deg)`,
|
||||
transformOrigin: "0 50%",
|
||||
}}
|
||||
@ -8511,8 +8525,8 @@ export function ImageNode({
|
||||
<div
|
||||
className="relative"
|
||||
style={{
|
||||
width: "56px",
|
||||
height: "56px",
|
||||
width: "60px",
|
||||
height: "60px",
|
||||
transform:
|
||||
"translate(-50%, -50%) rotateX(-18deg) rotateY(28deg)",
|
||||
transformStyle: "preserve-3d",
|
||||
@ -8548,14 +8562,14 @@ export function ImageNode({
|
||||
)}
|
||||
|
||||
{anglePanelMode === "subject" ? (
|
||||
<div className="pointer-events-none absolute bottom-[54px] left-[34px] z-[3] flex h-[48px] w-[48px] items-center justify-center rounded-[16px] border border-[#eceef3] bg-white shadow-[0_10px_24px_rgba(15,23,42,0.07)]">
|
||||
<div className="pointer-events-none absolute bottom-[36px] left-[34px] z-[3] flex h-[50px] w-[50px] items-center justify-center rounded-[16px] border border-[#eceef3] bg-white shadow-[0_10px_24px_rgba(15,23,42,0.07)]">
|
||||
<Box className="h-5 w-5 text-[#8a8f98]" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-7 space-y-6">
|
||||
<div className="mt-5 space-y-4">
|
||||
<AngleEditPanelSlider
|
||||
label="旋转"
|
||||
max={180}
|
||||
@ -8582,9 +8596,9 @@ export function ImageNode({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-7 grid grid-cols-2 gap-3">
|
||||
<div className="mt-5 grid grid-cols-2 gap-3">
|
||||
<button
|
||||
className="inline-flex h-14 items-center justify-center rounded-[18px] border border-[#dfe2e8] bg-white text-[18px] font-medium text-[#27292f] transition-colors hover:bg-[#f7f8fa]"
|
||||
className="inline-flex h-[50px] items-center justify-center rounded-[18px] border border-[#dfe2e8] bg-white text-[17px] font-medium text-[#27292f] transition-colors hover:bg-[#f7f8fa]"
|
||||
disabled={isApplyingAngleEdit}
|
||||
onClick={() => setAnglePanelOpen(false)}
|
||||
type="button"
|
||||
@ -8592,7 +8606,7 @@ export function ImageNode({
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
className="inline-flex h-14 items-center justify-center gap-2 rounded-[18px] bg-[#2d2f34] text-[18px] font-semibold text-white transition-colors hover:bg-[#23252a] disabled:cursor-not-allowed disabled:bg-[#cfd2d8] disabled:text-white/80"
|
||||
className="inline-flex h-[50px] items-center justify-center gap-2 rounded-[18px] bg-[#2d2f34] text-[17px] font-semibold text-white transition-colors hover:bg-[#23252a] disabled:cursor-not-allowed disabled:bg-[#cfd2d8] disabled:text-white/80"
|
||||
disabled={isApplyingAngleEdit}
|
||||
onClick={() => void handleApplyAngleEdit()}
|
||||
type="button"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user