This commit is contained in:
Haapy
2026-05-14 22:00:34 +00:00
parent 259959d713
commit fe9f3681fd
6 changed files with 59 additions and 30 deletions

1
dist/assets/index-DDld2teB.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-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}

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)}.canvas-container.pan-mode{cursor:grab}.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-CurFfSxZ.js"></script> <script type="module" crossorigin src="/assets/index-DYmGrN0R.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DuETBnoW.css"> <link rel="stylesheet" crossorigin href="/assets/index-DDld2teB.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -35,28 +35,47 @@ export function Canvas({ initialCards }: CanvasProps) {
}; };
}, []); }, []);
const onWheel = useCallback( // Suppress WebKit/GTK middle-click defaults (autoscroll on some platforms,
(e: React.WheelEvent) => { // paste primary selection on Linux). Must be a native listener — React's
if (!e.ctrlKey && !e.metaKey) return; // synthetic onPointerDown.preventDefault doesn't reliably stop these.
e.preventDefault(); useEffect(() => {
const rect = containerRef.current!.getBoundingClientRect(); const el = containerRef.current;
const mx = e.clientX - rect.left; if (!el) return;
const my = e.clientY - rect.top; const stopMiddle = (e: MouseEvent) => {
setVp((prev) => { if (e.button === 1) e.preventDefault();
const factor = Math.exp(-e.deltaY * ZOOM_SENSITIVITY); };
const next = Math.max(MIN_SCALE, Math.min(MAX_SCALE, prev.scale * factor)); const stopAux = (e: MouseEvent) => {
const k = next / prev.scale; if (e.button === 1) e.preventDefault();
return { x: mx - (mx - prev.x) * k, y: my - (my - prev.y) * k, scale: next }; };
}); el.addEventListener("mousedown", stopMiddle);
}, el.addEventListener("auxclick", stopAux);
[], return () => {
); el.removeEventListener("mousedown", stopMiddle);
el.removeEventListener("auxclick", stopAux);
};
}, []);
const onWheel = useCallback((e: React.WheelEvent) => {
if (!e.ctrlKey && !e.metaKey) return;
e.preventDefault();
const rect = containerRef.current!.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
setVp((prev) => {
const factor = Math.exp(-e.deltaY * ZOOM_SENSITIVITY);
const next = Math.max(MIN_SCALE, Math.min(MAX_SCALE, prev.scale * factor));
const k = next / prev.scale;
return { x: mx - (mx - prev.x) * k, y: my - (my - prev.y) * k, scale: next };
});
}, []);
const onPointerDown = (e: React.PointerEvent) => { const onPointerDown = (e: React.PointerEvent) => {
const isPan = e.button === 1 || (e.button === 0 && spaceHeld); const isPan = e.button === 1 || (e.button === 0 && spaceHeld);
if (!isPan) return; if (!isPan) return;
e.preventDefault(); e.preventDefault();
(e.target as Element).setPointerCapture(e.pointerId); // Capture on the container, not e.target — survives card re-renders and
// avoids odd behavior when the click lands on a textarea or child element.
containerRef.current?.setPointerCapture(e.pointerId);
panState.current = { startX: e.clientX, startY: e.clientY, vpX: vp.x, vpY: vp.y }; panState.current = { startX: e.clientX, startY: e.clientY, vpX: vp.x, vpY: vp.y };
}; };
@@ -69,7 +88,11 @@ export function Canvas({ initialCards }: CanvasProps) {
const onPointerUp = (e: React.PointerEvent) => { const onPointerUp = (e: React.PointerEvent) => {
if (panState.current) { if (panState.current) {
(e.target as Element).releasePointerCapture(e.pointerId); try {
containerRef.current?.releasePointerCapture(e.pointerId);
} catch {
// capture may already be released if pointercancel fired
}
panState.current = null; panState.current = null;
} }
}; };
@@ -81,7 +104,7 @@ export function Canvas({ initialCards }: CanvasProps) {
return ( return (
<div <div
ref={containerRef} ref={containerRef}
className={`canvas-container ${spaceHeld ? "pan-mode" : ""}`} className={`canvas-container ${spaceHeld ? "pan-mode" : ""} ${panState.current ? "panning" : ""}`}
onWheel={onWheel} onWheel={onWheel}
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
onPointerMove={onPointerMove} onPointerMove={onPointerMove}

View File

@@ -4,12 +4,18 @@
overflow: hidden; overflow: hidden;
cursor: default; cursor: default;
background: var(--bg); background: var(--bg);
touch-action: none;
overscroll-behavior: contain;
} }
.canvas-container.pan-mode { .canvas-container.pan-mode {
cursor: grab; cursor: grab;
} }
.canvas-container.panning {
cursor: grabbing;
}
.canvas-grid { .canvas-grid {
position: absolute; position: absolute;
inset: 0; inset: 0;