diff --git a/README.md b/README.md index 0cbf95a..a3149e3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The image builds the PWA and serves it with nginx. Put an HTTPS reverse proxy in ## Using it - **Tools** (bottom bar): Text `T`, Pen `P`, Highlighter `H`, Eraser `E`, Import PDF, Undo/Redo, zoom. The pen's eraser end erases too. -- **Pages**: the "+" circles beside the active page add a page there (a quick finger flick toward a free side offers one there; turn it off under Settings → Pen & touch); `Alt+Arrow` moves to (or creates) the neighbour. Right-click a page or use its `⋯` button to insert, resize or delete it. +- **Pages**: the "+" circles beside the active page add a page there (a quick finger fling past a free edge highlights that side's "+"); `Alt+Arrow` moves to (or creates) the neighbour. Right-click a page or use its `⋯` button to insert, resize or delete it. - **Grid**: the faint background grid has one cell per active page; Settings → Appearance can hide it or subdivide it. - **Importing a PDF** into a non-empty canvas shows every free spot; click one to place the chain. Into a blank canvas it simply replaces the empty page. The Files panel can also import a PDF as a new canvas. - **View**: wheel/trackpad pans, `Ctrl`+wheel or pinch zooms, `Ctrl+0` fits the page, `Ctrl+9` shows the whole tree, space-drag or middle-drag pans. diff --git a/spec.md b/spec.md index c2f5235..aaedd63 100644 --- a/spec.md +++ b/spec.md @@ -9,7 +9,7 @@ One canvas = one tree = one `.pdf` file. - A branch can only ever continue in the direction it started (strictly linear — no sub-branches off a branch). - A trunk page can have at most one left branch and one right branch. - **PDF import**: creates a chain of pages in whatever direction you pick when placing it (up/down = extends the trunk, left/right = becomes a branch off the trunk page you clicked). Only one trunk exists per canvas — additional imported PDFs must attach as a branch off some trunk page, or extend an existing branch further outward in its established direction. -- Each free side of the active page shows a small "+" circle just off its edge; tapping it (or `Alt+Arrow`) adds a blank page there. A quick finger fling toward a free side offers a page there for a few seconds: that side's "+" pulses, or, if it is off screen, an "Add page" button appears at that edge of the screen. The fling never adds a page by itself, and can be turned off in Settings. +- Each free side of the active page shows a small "+" circle just off its edge; tapping it (or `Alt+Arrow`) adds a blank page there. A quick finger fling past a free edge highlights that side's "+" for a few seconds, but never adds a page by itself. - The canvas background is a faint grid with one cell per active page, aligned to its edges; Settings can hide it or subdivide it. - Deleting a page removes it (undo via Ctrl+Z); no permanent trash. - Default zoom: one page fills the screen. Zoom out reveals the tree. diff --git a/src/lib/components/CanvasView.svelte b/src/lib/components/CanvasView.svelte index 6031c8a..4746268 100644 --- a/src/lib/components/CanvasView.svelte +++ b/src/lib/components/CanvasView.svelte @@ -119,8 +119,7 @@ const placeSlots = $derived(tools.placing ? freeSlots(doc.tree) : []); - // A finger fling toward a free side of the active page offers a new page there: the plus - // pulses if it's on screen, otherwise a button appears at that edge of the screen. + // A finger fling past the active page's free edge highlights the plus on that side. let offer = $state(null); let offerTimer: ReturnType | undefined; @@ -131,21 +130,18 @@ } function offerAfterFling(samples: Sample[]) { - if (!settings.data.flingToAdd) return; const dir = flingDir(samples); const id = doc.activeId; + const r = id && doc.rects.get(id); const slot = dir && ghostSlots.find((s) => s.anchor === id && s.dir === dir); - if (slot) setOffer(slot); - } - - /** The offered plus is off screen, so the offer shows as an edge-of-screen button instead. */ - const offerOffscreen = $derived.by(() => { - const r = offer && plusRect(offer); - if (!r) return false; + if (!r || !slot) return; + // Only once that edge has come into view, i.e. the fling went past the end. const a = viewport.toScreen(r.x, r.y); const b = viewport.toScreen(r.x + r.width, r.y + r.height); - return b.x < 0 || b.y < 0 || a.x > viewport.width || a.y > viewport.height; - }); + const edge = { left: a.x, right: b.x, up: a.y, down: b.y }[slot.dir]; + const size = slot.dir === 'left' || slot.dir === 'right' ? viewport.width : viewport.height; + if (edge >= 0 && edge <= size) setOffer(slot); + } // Show every possible spot when an imported PDF is waiting to be placed. $effect(() => { @@ -342,7 +338,7 @@ const fingerInks = tools.inking && settings.data.fingerDraw && !palm.penSeen; // Taps on an add-page button are palm-checked like navigation: still rejected for // pen-down, palm-sized contacts and right after pen use, but not for the 5 s pen session. - const plus = target.closest('.ghost, .offer-pill'); + const plus = target.closest('.ghost'); if (e.pointerType === 'touch') { const decision = palm.evaluate(e, touchIntent(fingerInks, !!plus)); if (!decision.accept) return; // palm: ignore entirely @@ -711,20 +707,6 @@ {/if} - {#if offer && offerOffscreen} - - - {/if} - {#if eraserAt && tools.tool === 'eraser'}
{/if} @@ -852,41 +834,6 @@ transform: scale(1.3); } } - .offer-pill { - position: absolute; - display: flex; - align-items: center; - gap: 6px; - padding: 10px 16px 10px 12px; - border: 1px solid var(--accent); - border-radius: 999px; - background: var(--bg); - color: var(--accent); - box-shadow: var(--shadow); - cursor: pointer; - white-space: nowrap; - } - .offer-pill[data-side='right'] { - right: 12px; - top: 50%; - transform: translateY(-50%); - } - .offer-pill[data-side='left'] { - left: 12px; - top: 50%; - transform: translateY(-50%); - } - .offer-pill[data-side='up'] { - top: 12px; - left: 50%; - transform: translateX(-50%); - } - .offer-pill[data-side='down'] { - /* Clear of the toolbar. */ - bottom: 72px; - left: 50%; - transform: translateX(-50%); - } @media (prefers-reduced-motion: reduce) { .ghost.offer .plus { animation: none; diff --git a/src/lib/components/SettingsModal.svelte b/src/lib/components/SettingsModal.svelte index a6be752..9c8f886 100644 --- a/src/lib/components/SettingsModal.svelte +++ b/src/lib/components/SettingsModal.svelte @@ -103,10 +103,6 @@
Draw with finger
When off, a single finger always pans; only pen and mouse draw. Once you use a pen, fingers only pan and zoom.
-
-
Swipe to add pages
A quick one-finger flick toward a free side of the active page offers to add a page there.
- -

Palm rejection

Pen session lockout
Ignore touches while the pen is writing, and finger ink for 5 seconds after it was used.
diff --git a/src/lib/state/settings.svelte.ts b/src/lib/state/settings.svelte.ts index ceb10c2..92cee0b 100644 --- a/src/lib/state/settings.svelte.ts +++ b/src/lib/state/settings.svelte.ts @@ -26,8 +26,6 @@ interface SettingsData { pageStyle: PageStylePref; grid: GridSettings; fingerDraw: boolean; - /** A finger fling toward a free side of the active page offers a new page there. */ - flingToAdd: boolean; palm: PalmOptions; github: GitHubSettings; /** Minutes between automatic pushes (0 = manual only). */ @@ -41,7 +39,6 @@ const DEFAULTS: SettingsData = { pageStyle: 'match', grid: { show: true, divisions: 1 }, fingerDraw: true, - flingToAdd: true, palm: DEFAULT_PALM, github: { token: '', owner: '', repo: '', branch: 'main', dir: '' }, pushInterval: 5,