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

1
dist/assets/index-BKho0BkD.css vendored Normal file
View File

@@ -0,0 +1 @@
.canvas-container{position:fixed;top:0;right:0;bottom:0;left:0;overflow:hidden;cursor:default;background:var(--bg);touch-action:none;overscroll-behavior:contain}.canvas-container.pan-mode{cursor:grab}.canvas-container.panning{cursor:grabbing}.canvas-grid{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;background-image:radial-gradient(circle,var(--bg-grid) 1px,transparent 1px);background-repeat:repeat}.canvas-hud{position:fixed;bottom:12px;left:12px;display:flex;gap:12px;padding:6px 10px;background:#00000080;border-radius:6px;font-size:11px;font-family:ui-monospace,Menlo,monospace;color:var(--text);pointer-events:none;z-index:1000}.card{position:absolute;background:var(--card-bg);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)) #0000004d;overflow:hidden;display:flex;flex-direction:column}.card-header{padding:.46em .77em;background:#0003;border-bottom:calc(1px * var(--scale, 1)) solid var(--card-border);font-size:.85em;cursor:move;flex-shrink:0}.card-body{flex:1;overflow:auto}.note-card textarea{width:100%;height:100%;background:transparent;border:none;outline:none;resize:none;color:var(--text);font-family:inherit;font-size:1em;padding:.77em}:root{--bg: #1a1a1f;--bg-grid: #25252c;--card-bg: #2a2a32;--card-border: #3a3a45;--text: #e8e8ec;--accent: #6a8cff;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;color-scheme:dark}*{box-sizing:border-box;margin:0;padding:0}html,body,#root{width:100%;height:100%;overflow:hidden;background:var(--bg);color:var(--text);-webkit-user-select:none;user-select:none}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.canvas-container{position:fixed;top:0;right:0;bottom:0;left:0;overflow:hidden;cursor:default;background:var(--bg);touch-action:none;overscroll-behavior:contain}.canvas-container.pan-mode{cursor:grab}.canvas-container.panning{cursor:grabbing}.canvas-grid{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;background-image:radial-gradient(circle,var(--bg-grid) 1px,transparent 1px);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;left:12px;display:flex;gap:12px;padding:6px 10px;background:#00000080;border-radius:6px;font-size:11px;font-family:ui-monospace,Menlo,monospace;color:var(--text);pointer-events:none}.card{position:absolute;background:var(--card-bg);border:1px solid var(--card-border);border-radius:8px;box-shadow:0 4px 16px #0000004d;overflow:hidden;display:flex;flex-direction:column}.card-header{padding:6px 10px;background:#0003;border-bottom:1px solid var(--card-border);font-size:11px;cursor:move;flex-shrink:0}.card-body{flex:1;overflow:auto}.note-card textarea{width:100%;height:100%;background:transparent;border:none;outline:none;resize:none;color:var(--text);font-family:inherit;font-size:13px;padding:10px}:root{--bg: #1a1a1f;--bg-grid: #25252c;--card-bg: #2a2a32;--card-border: #3a3a45;--text: #e8e8ec;--accent: #6a8cff;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;color-scheme:dark}*{box-sizing:border-box;margin:0;padding:0}html,body,#root{width:100%;height:100%;overflow:hidden;background:var(--bg);color:var(--text);-webkit-user-select:none;user-select:none}

4
dist/index.html vendored
View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Infinite</title> <title>Infinite</title>
<script type="module" crossorigin src="/assets/index-CK2X1Wwi.js"></script> <script type="module" crossorigin src="/assets/index-CdDqLPP_.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DDld2teB.css"> <link rel="stylesheet" crossorigin href="/assets/index-BKho0BkD.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

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

View File

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

View File

@@ -1,12 +1,16 @@
import { useRef } from "react"; import { useRef } from "react";
import type { NoteCard } from "../types"; import type { NoteCard } from "../types";
import { useViewport } from "../viewport";
interface Props { interface Props {
card: NoteCard; card: NoteCard;
onUpdate: (patch: Partial<NoteCard>) => void; onUpdate: (patch: Partial<NoteCard>) => void;
} }
const BODY_FONT_BASE = 13;
export function NoteCardView({ card, onUpdate }: Props) { export function NoteCardView({ card, onUpdate }: Props) {
const vp = useViewport();
const dragState = useRef<{ startX: number; startY: number; cardX: number; cardY: number } | null>( const dragState = useRef<{ startX: number; startY: number; cardX: number; cardY: number } | null>(
null, null,
); );
@@ -19,25 +23,43 @@ export function NoteCardView({ card, onUpdate }: Props) {
}; };
const onHeaderPointerMove = (e: React.PointerEvent) => { const onHeaderPointerMove = (e: React.PointerEvent) => {
if (!dragState.current) return; const ds = dragState.current;
const worldEl = (e.currentTarget as HTMLElement).closest(".canvas-world") as HTMLElement; if (!ds) return;
const scale = worldEl ? parseTransformScale(worldEl.style.transform) : 1; const dx = (e.clientX - ds.startX) / vp.scale;
const dx = (e.clientX - dragState.current.startX) / scale; const dy = (e.clientY - ds.startY) / vp.scale;
const dy = (e.clientY - dragState.current.startY) / scale; onUpdate({ x: ds.cardX + dx, y: ds.cardY + dy });
onUpdate({ x: dragState.current.cardX + dx, y: dragState.current.cardY + dy });
}; };
const onHeaderPointerUp = (e: React.PointerEvent) => { const onHeaderPointerUp = (e: React.PointerEvent) => {
if (dragState.current) { if (dragState.current) {
try {
(e.target as Element).releasePointerCapture(e.pointerId); (e.target as Element).releasePointerCapture(e.pointerId);
} catch {
// already released
}
dragState.current = null; 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 ( return (
<div <div
className="card note-card" 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 <div
className="card-header" className="card-header"
@@ -58,8 +80,3 @@ export function NoteCardView({ card, onUpdate }: Props) {
</div> </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);