fix zomming

This commit is contained in:
Haapy
2026-05-14 22:12:32 +00:00
parent a858a3415d
commit eaeb4c2d92
8 changed files with 59 additions and 47 deletions

View File

@@ -1,5 +1,6 @@
import { useEffect, useRef, useState, useCallback } from "react";
import type { Card, Viewport } from "./types";
import { ViewportContext } from "./viewport";
import { NoteCardView } from "./cards/NoteCardView";
import "./canvas.css";
@@ -119,17 +120,14 @@ export function Canvas({ initialCards }: CanvasProps) {
backgroundSize: `${40 * vp.scale}px ${40 * vp.scale}px`,
}}
/>
<div
className="canvas-world"
style={{ transform: `translate(${vp.x}px, ${vp.y}px) scale(${vp.scale})` }}
>
<ViewportContext.Provider value={vp}>
{cards.map((c) => {
if (c.kind === "note") {
return <NoteCardView key={c.id} card={c} onUpdate={(p) => updateCard(c.id, p)} />;
}
return null;
})}
</div>
</ViewportContext.Provider>
<div className="canvas-hud">
<span>x {vp.x.toFixed(0)}</span>
<span>y {vp.y.toFixed(0)}</span>

View File

@@ -25,15 +25,6 @@
background-repeat: repeat;
}
.canvas-world {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
transform-origin: 0 0;
}
.canvas-hud {
position: fixed;
bottom: 12px;
@@ -47,24 +38,25 @@
font-family: ui-monospace, Menlo, monospace;
color: var(--text);
pointer-events: none;
z-index: 1000;
}
.card {
position: absolute;
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
border: calc(1px * var(--scale, 1)) solid var(--card-border);
border-radius: calc(8px * var(--scale, 1));
box-shadow: 0 calc(4px * var(--scale, 1)) calc(16px * var(--scale, 1)) rgba(0, 0, 0, 0.3);
overflow: hidden;
display: flex;
flex-direction: column;
}
.card-header {
padding: 6px 10px;
padding: 0.46em 0.77em;
background: rgba(0, 0, 0, 0.2);
border-bottom: 1px solid var(--card-border);
font-size: 11px;
border-bottom: calc(1px * var(--scale, 1)) solid var(--card-border);
font-size: 0.85em;
cursor: move;
flex-shrink: 0;
}
@@ -83,6 +75,6 @@
resize: none;
color: var(--text);
font-family: inherit;
font-size: 13px;
padding: 10px;
font-size: 1em;
padding: 0.77em;
}

View File

@@ -1,12 +1,16 @@
import { useRef } from "react";
import type { NoteCard } from "../types";
import { useViewport } from "../viewport";
interface Props {
card: NoteCard;
onUpdate: (patch: Partial<NoteCard>) => void;
}
const BODY_FONT_BASE = 13;
export function NoteCardView({ card, onUpdate }: Props) {
const vp = useViewport();
const dragState = useRef<{ startX: number; startY: number; cardX: number; cardY: number } | null>(
null,
);
@@ -19,25 +23,43 @@ export function NoteCardView({ card, onUpdate }: Props) {
};
const onHeaderPointerMove = (e: React.PointerEvent) => {
if (!dragState.current) return;
const worldEl = (e.currentTarget as HTMLElement).closest(".canvas-world") as HTMLElement;
const scale = worldEl ? parseTransformScale(worldEl.style.transform) : 1;
const dx = (e.clientX - dragState.current.startX) / scale;
const dy = (e.clientY - dragState.current.startY) / scale;
onUpdate({ x: dragState.current.cardX + dx, y: dragState.current.cardY + dy });
const ds = dragState.current;
if (!ds) return;
const dx = (e.clientX - ds.startX) / vp.scale;
const dy = (e.clientY - ds.startY) / vp.scale;
onUpdate({ x: ds.cardX + dx, y: ds.cardY + dy });
};
const onHeaderPointerUp = (e: React.PointerEvent) => {
if (dragState.current) {
(e.target as Element).releasePointerCapture(e.pointerId);
try {
(e.target as Element).releasePointerCapture(e.pointerId);
} catch {
// already released
}
dragState.current = null;
}
};
const screenLeft = vp.x + card.x * vp.scale;
const screenTop = vp.y + card.y * vp.scale;
const screenW = card.width * vp.scale;
const screenH = card.height * vp.scale;
return (
<div
className="card note-card"
style={{ left: card.x, top: card.y, width: card.width, height: card.height, zIndex: card.z }}
style={
{
left: screenLeft,
top: screenTop,
width: screenW,
height: screenH,
zIndex: card.z,
fontSize: BODY_FONT_BASE * vp.scale,
"--scale": vp.scale,
} as React.CSSProperties
}
>
<div
className="card-header"
@@ -58,8 +80,3 @@ export function NoteCardView({ card, onUpdate }: Props) {
</div>
);
}
function parseTransformScale(transform: string): number {
const m = transform.match(/scale\(([^)]+)\)/);
return m ? parseFloat(m[1]) : 1;
}

5
src/canvas/viewport.ts Normal file
View File

@@ -0,0 +1,5 @@
import { createContext, useContext } from "react";
import type { Viewport } from "./types";
export const ViewportContext = createContext<Viewport>({ x: 0, y: 0, scale: 1 });
export const useViewport = () => useContext(ViewportContext);