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 name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Infinite</title>
<script type="module" crossorigin src="/assets/index-CurFfSxZ.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DuETBnoW.css">
<script type="module" crossorigin src="/assets/index-DYmGrN0R.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DDld2teB.css">
</head>
<body>
<div id="root"></div>

View File

@@ -35,28 +35,47 @@ export function Canvas({ initialCards }: CanvasProps) {
};
}, []);
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 };
});
},
[],
);
// Suppress WebKit/GTK middle-click defaults (autoscroll on some platforms,
// paste primary selection on Linux). Must be a native listener — React's
// synthetic onPointerDown.preventDefault doesn't reliably stop these.
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const stopMiddle = (e: MouseEvent) => {
if (e.button === 1) e.preventDefault();
};
const stopAux = (e: MouseEvent) => {
if (e.button === 1) e.preventDefault();
};
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 isPan = e.button === 1 || (e.button === 0 && spaceHeld);
if (!isPan) return;
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 };
};
@@ -69,7 +88,11 @@ export function Canvas({ initialCards }: CanvasProps) {
const onPointerUp = (e: React.PointerEvent) => {
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;
}
};
@@ -81,7 +104,7 @@ export function Canvas({ initialCards }: CanvasProps) {
return (
<div
ref={containerRef}
className={`canvas-container ${spaceHeld ? "pan-mode" : ""}`}
className={`canvas-container ${spaceHeld ? "pan-mode" : ""} ${panState.current ? "panning" : ""}`}
onWheel={onWheel}
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}

View File

@@ -4,12 +4,18 @@
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;
inset: 0;