Pen session lockout
Ignore touches while the pen is writing, and finger ink for 5 seconds after it was used.
@@ -209,14 +187,6 @@
color: var(--faint);
padding: 0 10px 8px;
}
- .build {
- margin-top: auto;
- padding: 0 10px;
- font-family: var(--mono-font);
- font-size: 11px;
- color: var(--faint);
- user-select: text;
- }
nav button {
text-align: left;
border: 0;
diff --git a/src/lib/ink/fling.test.ts b/src/lib/ink/fling.test.ts
deleted file mode 100644
index ec9ef97..0000000
--- a/src/lib/ink/fling.test.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { describe, expect, it } from 'vitest';
-import { flingDir, pushSample, type Sample } from './fling';
-
-/** A straight drag from (0,0) by (dx,dy) over `ms`, sampled every 16 ms. */
-function drag(dx: number, dy: number, ms: number): Sample[] {
- const out: Sample[] = [];
- for (let t = 0; t <= ms; t += 16) pushSample(out, { x: (dx * t) / ms, y: (dy * t) / ms, t });
- return out;
-}
-
-describe('flingDir', () => {
- it('maps a flick to the side it reveals', () => {
- expect(flingDir(drag(-200, 0, 96))).toBe('right');
- expect(flingDir(drag(200, 0, 96))).toBe('left');
- expect(flingDir(drag(0, -200, 96))).toBe('down');
- expect(flingDir(drag(0, 200, 96))).toBe('up');
- });
-
- it('ignores slow drags', () => {
- expect(flingDir(drag(-200, 0, 800))).toBeNull();
- });
-
- it('ignores diagonal flicks', () => {
- expect(flingDir(drag(-200, -150, 96))).toBeNull();
- });
-
- it('ignores a drag that stops before lifting', () => {
- const s = drag(-300, 0, 96);
- const end = s.at(-1)!;
- for (let t = 16; t <= 160; t += 16) pushSample(s, { ...end, t: end.t + t });
- expect(flingDir(s)).toBeNull();
- });
-
- it('needs at least two samples', () => {
- expect(flingDir([])).toBeNull();
- expect(flingDir([{ x: 0, y: 0, t: 0 }])).toBeNull();
- });
-
- it('keeps the trail short', () => {
- const s = drag(-2000, 0, 2000);
- expect(s.at(-1)!.t - s[0].t).toBeLessThanOrEqual(216);
- });
-});
diff --git a/src/lib/ink/fling.ts b/src/lib/ink/fling.ts
deleted file mode 100644
index 2b2a689..0000000
--- a/src/lib/ink/fling.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-// Fling detection for one-finger pans: a quick flick that ends the drag.
-
-import type { Dir } from '$lib/model/tree';
-
-export interface Sample {
- x: number;
- y: number;
- t: number;
-}
-
-/** Only the last stretch of the drag counts. */
-const WINDOW_MS = 100;
-/** px/ms; a slow drag that stops is not a fling. */
-const MIN_SPEED = 0.6;
-/** The main axis must dominate by this much. */
-const MIN_RATIO = 2;
-
-/**
- * Side of the canvas a fling heads for, or null. Moving the finger left pulls in
- * what's on the right, so a leftward flick returns 'right'.
- */
-export function flingDir(samples: readonly Sample[]): Dir | null {
- const last = samples.at(-1);
- if (!last) return null;
- const first = samples.find((s) => last.t - s.t <= WINDOW_MS);
- if (!first || first === last) return null;
- const dt = last.t - first.t;
- if (dt <= 0) return null;
- const dx = last.x - first.x;
- const dy = last.y - first.y;
- if (Math.hypot(dx, dy) / dt < MIN_SPEED) return null;
- if (Math.abs(dx) >= Math.abs(dy) * MIN_RATIO) return dx < 0 ? 'right' : 'left';
- if (Math.abs(dy) >= Math.abs(dx) * MIN_RATIO) return dy < 0 ? 'down' : 'up';
- return null;
-}
-
-/** Keeps a short trail of pointer samples for flingDir. */
-export function pushSample(samples: Sample[], s: Sample) {
- samples.push(s);
- while (samples.length > 2 && s.t - samples[0].t > WINDOW_MS * 2) samples.shift();
-}
diff --git a/src/lib/ink/palm.test.ts b/src/lib/ink/palm.test.ts
index add4644..473358c 100644
--- a/src/lib/ink/palm.test.ts
+++ b/src/lib/ink/palm.test.ts
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
-import { PalmRejector, touchIntent } from './palm';
+import { PalmRejector } from './palm';
const ev = (type: string, pointerType: string, extra: Partial
= {}) =>
({ type, pointerType, pointerId: pointerType === 'pen' ? 1 : 2, width: 10, height: 10, ...extra }) as PointerEvent;
@@ -53,37 +53,4 @@ describe('palm rejection', () => {
it('allows finger ink again once the pen session ends', () => {
expect(afterPen(5100).evaluate(touch(), 'draw').accept).toBe(true);
});
-
- describe('add-page button taps', () => {
- // With a pen in use fingers don't ink, but finger ink may still be on (before any pen).
- const tap = (palm: PalmRejector, extra: Partial = {}, fingerInks = false) =>
- palm.evaluate(touch(extra), touchIntent(fingerInks, true));
-
- it('are handled as navigation even when fingers ink', () => {
- expect(touchIntent(true, true)).toBe('navigate');
- expect(touchIntent(false, true)).toBe('navigate');
- expect(touchIntent(true, false)).toBe('draw');
- expect(touchIntent(false, false)).toBe('navigate');
- });
-
- it('are accepted a couple of seconds after writing', () => {
- expect(tap(afterPen(2000)).accept).toBe(true);
- expect(tap(afterPen(2000), {}, true).accept).toBe(true);
- });
-
- it('are accepted while the pen hovers', () => {
- expect(tap(afterPen(2000, { leave: false })).accept).toBe(true);
- });
-
- it('are rejected while the pen is down', () => {
- const palm = new PalmRejector();
- palm.track(ev('pointerdown', 'pen'));
- expect(tap(palm).accept).toBe(false);
- });
-
- it('are rejected for a palm or right after the pen', () => {
- expect(tap(afterPen(2000), { width: 60, height: 40 }).accept).toBe(false);
- expect(tap(afterPen(50)).accept).toBe(false);
- });
- });
});
diff --git a/src/lib/ink/palm.ts b/src/lib/ink/palm.ts
index 2c9a233..7030b2b 100644
--- a/src/lib/ink/palm.ts
+++ b/src/lib/ink/palm.ts
@@ -27,15 +27,6 @@ const PALM_CONTACT_PX = 35;
/** What an accepted touch would do: ink (draw/erase) or pan/zoom. */
export type TouchIntent = 'draw' | 'navigate';
-/**
- * Which policy a touch falls under. It only draws when finger ink is on and it
- * didn't land on a button; taps on buttons (e.g. add page) navigate, so they keep
- * working between pen strokes.
- */
-export function touchIntent(fingerInks: boolean, onButton: boolean): TouchIntent {
- return fingerInks && !onButton ? 'draw' : 'navigate';
-}
-
export interface Decision {
accept: boolean;
reason: string;
diff --git a/src/lib/state/settings.svelte.ts b/src/lib/state/settings.svelte.ts
index ceb10c2..4fa5724 100644
--- a/src/lib/state/settings.svelte.ts
+++ b/src/lib/state/settings.svelte.ts
@@ -6,12 +6,6 @@ import { DEFAULT_PALM, type PalmOptions } from '$lib/ink/palm';
export type ThemePref = 'system' | 'light' | 'dark';
export type PageStylePref = 'paper' | 'match';
-export interface GridSettings {
- show: boolean;
- /** Minor lines per page cell side (1 = page-sized cells only). */
- divisions: number;
-}
-
export interface GitHubSettings {
token: string;
owner: string;
@@ -24,10 +18,7 @@ export interface GitHubSettings {
interface SettingsData {
theme: ThemePref;
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). */
@@ -39,9 +30,7 @@ interface SettingsData {
const DEFAULTS: SettingsData = {
theme: 'system',
pageStyle: 'match',
- grid: { show: true, divisions: 1 },
fingerDraw: true,
- flingToAdd: true,
palm: DEFAULT_PALM,
github: { token: '', owner: '', repo: '', branch: 'main', dir: '' },
pushInterval: 5,
@@ -59,7 +48,6 @@ function load(): SettingsData {
return {
...DEFAULTS,
...saved,
- grid: { ...DEFAULTS.grid, ...saved.grid },
palm: { ...DEFAULTS.palm, ...saved.palm },
github: { ...DEFAULTS.github, ...saved.github }
};
diff --git a/vite.config.ts b/vite.config.ts
index c94dbc9..3f382a5 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,40 +1,8 @@
-import { execSync } from 'node:child_process';
-import { existsSync, readFileSync } from 'node:fs';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
-/** Current commit. Uses git when available, else reads .git directly (the Docker build has no git binary). */
-function gitCommit(): { commit: string; dirty: boolean } {
- if (process.env.GIT_COMMIT) return { commit: process.env.GIT_COMMIT, dirty: false };
- try {
- const commit = execSync('git rev-parse HEAD', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
- const dirty = execSync('git status --porcelain --untracked-files=no', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim() !== '';
- return { commit, dirty };
- } catch {
- /* fall through */
- }
- try {
- const head = readFileSync('.git/HEAD', 'utf8').trim();
- if (!head.startsWith('ref: ')) return { commit: head, dirty: false };
- const ref = head.slice(5);
- if (existsSync(`.git/${ref}`)) return { commit: readFileSync(`.git/${ref}`, 'utf8').trim(), dirty: false };
- const packed = readFileSync('.git/packed-refs', 'utf8').split('\n').find((l) => l.endsWith(` ${ref}`));
- if (packed) return { commit: packed.split(' ')[0], dirty: false };
- } catch {
- /* no repository */
- }
- return { commit: 'unknown', dirty: false };
-}
-
-const build = {
- version: JSON.parse(readFileSync('package.json', 'utf8')).version as string,
- ...gitCommit(),
- time: new Date().toISOString()
-};
-
export default defineConfig({
plugins: [sveltekit()],
- define: { __BUILD__: JSON.stringify(build) },
test: {
include: ['src/**/*.test.ts'],
environment: 'node'