fix zomming
This commit is contained in:
1
dist/assets/index-BKho0BkD.css
vendored
Normal file
1
dist/assets/index-BKho0BkD.css
vendored
Normal 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
1
dist/assets/index-DDld2teB.css
vendored
1
dist/assets/index-DDld2teB.css
vendored
@@ -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
4
dist/index.html
vendored
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
(e.target as Element).releasePointerCapture(e.pointerId);
|
try {
|
||||||
|
(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
5
src/canvas/viewport.ts
Normal 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);
|
||||||
Reference in New Issue
Block a user