Add rulers and cm measurements to personalization canvas
- Display X and Y rulers with 2cm increments - Show position and size in centimeters (cm) instead of pixels - Calculate cmPerPx from product print area for accurate conversion - Rulers positioned above and to the left of design canvas - Labels show cm measurements for easy reference while personalizing Users can now see exact placement and dimensions in cm during design, with visual ruler guides for precise positioning. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
209af186b3
commit
c60491deca
@@ -200,6 +200,77 @@ function PrintSurface({
|
|||||||
<div className="pointer-events-none absolute inset-0" style={{ width: DISPLAY, height: DISPLAY }}>
|
<div className="pointer-events-none absolute inset-0" style={{ width: DISPLAY, height: DISPLAY }}>
|
||||||
{photo}
|
{photo}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Horizontal Ruler */}
|
||||||
|
<div
|
||||||
|
className="absolute pointer-events-none bg-paper border-b border-line text-xs font-mono"
|
||||||
|
style={{
|
||||||
|
left: stageLeft,
|
||||||
|
top: stageTop - 20,
|
||||||
|
width: stageW,
|
||||||
|
height: 18,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Array.from({ length: Math.ceil((stageW / (10 * (product.printAreaWidthMm / 100)))) }).map((_, i) => {
|
||||||
|
const cm = i * 2;
|
||||||
|
const px = (cm * stageW) / (product.printAreaWidthMm / 10);
|
||||||
|
if (px > stageW) return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`h-${cm}`}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: `${px}px`,
|
||||||
|
fontSize: '9px',
|
||||||
|
color: '#888',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cm}cm
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Vertical Ruler */}
|
||||||
|
<div
|
||||||
|
className="absolute pointer-events-none bg-paper border-r border-line text-xs font-mono"
|
||||||
|
style={{
|
||||||
|
left: stageLeft - 28,
|
||||||
|
top: stageTop,
|
||||||
|
width: 26,
|
||||||
|
height: stageH,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
paddingRight: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Array.from({ length: Math.ceil((stageH / (10 * (product.printAreaWidthMm / 100)))) }).map((_, i) => {
|
||||||
|
const cm = i * 2;
|
||||||
|
const px = (cm * stageH) / (product.printAreaWidthMm / 10);
|
||||||
|
if (px > stageH) return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`v-${cm}`}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: `${px}px`,
|
||||||
|
fontSize: '9px',
|
||||||
|
color: '#888',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cm}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="absolute" style={{ left: stageLeft, top: stageTop, width: stageW, height: stageH }}>
|
<div className="absolute" style={{ left: stageLeft, top: stageTop, width: stageW, height: stageH }}>
|
||||||
<Stage
|
<Stage
|
||||||
ref={stageRef}
|
ref={stageRef}
|
||||||
@@ -348,6 +419,11 @@ export default function DesignCanvas({ product }: { product: ProductDTO }) {
|
|||||||
const activePrintArea = view === 'front' ? frontPrintArea : backPrintArea;
|
const activePrintArea = view === 'front' ? frontPrintArea : backPrintArea;
|
||||||
const activeStageW = activePrintArea.wPct * DISPLAY;
|
const activeStageW = activePrintArea.wPct * DISPLAY;
|
||||||
const activeStageH = activePrintArea.hPct * DISPLAY;
|
const activeStageH = activePrintArea.hPct * DISPLAY;
|
||||||
|
|
||||||
|
// Calculate cm per pixel for display
|
||||||
|
const printAreaWidthPx = activePrintArea.wPct * DISPLAY;
|
||||||
|
const cmPerPx = (product.printAreaWidthMm / printAreaWidthPx) / 10;
|
||||||
|
const pxToCm = (px: number) => (px * cmPerPx).toFixed(2);
|
||||||
const setActiveElements = view === 'front' ? setElementsFront : setElementsBack;
|
const setActiveElements = view === 'front' ? setElementsFront : setElementsBack;
|
||||||
|
|
||||||
// Keep whichever transformer actually owns the selected element in sync, and
|
// Keep whichever transformer actually owns the selected element in sync, and
|
||||||
@@ -779,25 +855,25 @@ export default function DesignCanvas({ product }: { product: ProductDTO }) {
|
|||||||
|
|
||||||
{selected && (
|
{selected && (
|
||||||
<div className="mt-4 border border-line bg-surface p-4">
|
<div className="mt-4 border border-line bg-surface p-4">
|
||||||
<p className="mb-3 font-medium text-sm">Position & Size</p>
|
<p className="mb-3 font-medium text-sm">Position & Size (cm)</p>
|
||||||
<div className="space-y-2 text-xs font-mono">
|
<div className="space-y-2 text-xs font-mono">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-muted">X:</span>
|
<span className="text-muted">X:</span>
|
||||||
<span>{Math.round(selected.x)} px</span>
|
<span>{pxToCm(selected.x)} cm</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-muted">Y:</span>
|
<span className="text-muted">Y:</span>
|
||||||
<span>{Math.round(selected.y)} px</span>
|
<span>{pxToCm(selected.y)} cm</span>
|
||||||
</div>
|
</div>
|
||||||
{selected.type === 'image' && (
|
{selected.type === 'image' && (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-muted">Width:</span>
|
<span className="text-muted">Width:</span>
|
||||||
<span>{Math.round(selected.width)} px</span>
|
<span>{pxToCm(selected.width)} cm</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-muted">Height:</span>
|
<span className="text-muted">Height:</span>
|
||||||
<span>{Math.round(selected.height)} px</span>
|
<span>{pxToCm(selected.height)} cm</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user