Add second adapter, thumbnails, a11y pass, and a real test suite
- Immich adapter (adapters/immich.js): swipe a self-hosted photo library over its REST API, proving the adapter contract works for remote sources, not just the filesystem. - Refactor the preview contract from resolvePreviewPath() to streamPreview()/streamThumbnail(), so adapters can serve previews from anywhere (local file, proxied fetch, etc). - macOS Quick Look-backed thumbnail cache (lib/thumbnails.js): real resized thumbnails, HEIC/HEIF, and video poster frames, with a hard timeout and graceful fallback to the original file everywhere else. - Native "Browse..." folder picker (osascript) in Settings, with manual typing as the fallback on other platforms. - Confirm-guarded "Empty trash" action -- the only place this project permanently deletes anything. - Session resume: progress now survives a server restart and an unchanged Rescan via .swipeanything-session.json. - Accessibility pass: live region item announcements, aria-labels on action buttons, focus-trapped shortcuts dialog with focus restore, visible focus rings, a real role="progressbar", and <button>s instead of <a href="#"> in the header. - node:test suite (23 tests) covering the folder adapter, the Immich adapter (mocked fetch, no live server needed), and the HTTP API end-to-end; wired up as `npm test`. - Also includes the keyboard shortcuts (up/down undo/skip), swipe stamps, and shortcuts modal from the previous session that hadn't been committed yet.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
# SwipeAnything
|
||||
|
||||
Unleash the swipe on anything. Point it at a folder, and (via adapters) at
|
||||
your inbox, a database table, or whatever else you need to triage — one
|
||||
card at a time, right = keep, left = reject, like a dating app for your
|
||||
backlog.
|
||||
Unleash the swipe on anything. Point it at a folder or a photo library, and
|
||||
(via adapters) at your inbox, a database table, or whatever else you need to
|
||||
triage — one card at a time, right = keep, left = reject, like a dating app
|
||||
for your backlog.
|
||||
|
||||
## Why
|
||||
|
||||
@@ -20,20 +20,33 @@ what's behind them. Write a new adapter and you get the whole UI for free.
|
||||
|
||||
## Status
|
||||
|
||||
v1 ships with one polished adapter — **local folder** — plus the framework
|
||||
for more. Email, database-row, and other adapters are welcome as
|
||||
contributions; see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
Two adapters ship today — **local folder** and **Immich** (self-hosted photo
|
||||
library) — proving the framework works for both a filesystem and a remote
|
||||
API. Email, database-row, and other adapters are welcome as contributions;
|
||||
see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## Features
|
||||
|
||||
- Swipe (touch/mouse drag) or use the keyboard
|
||||
- Non-destructive by default: "reject" moves files to a `.swipeanything-trash/`
|
||||
folder next to the source, never a hard delete
|
||||
folder (or, for Immich, the library's own trash) — never a hard delete.
|
||||
A separate, confirm-guarded "Empty trash" action is the only place that
|
||||
permanently deletes anything.
|
||||
- Undo, any number of steps back
|
||||
- Session resume: closing the tab, reloading, or restarting the server picks
|
||||
up right where you left off (as long as the underlying items haven't changed)
|
||||
- Real thumbnails on macOS (via Quick Look), including HEIC/HEIF and video
|
||||
poster frames, with automatic fallback to the original file everywhere else
|
||||
- Native "Browse…" folder picker on macOS; type a path anywhere else
|
||||
- Live progress + per-action counts
|
||||
- Generic settings UI: every adapter declares its own config fields and gets
|
||||
a form for free — no adapter-specific frontend code required
|
||||
- Accessible by default: live region announcements per card, labeled action
|
||||
buttons, a focus-trapped shortcuts dialog, visible focus rings, and a real
|
||||
`role="progressbar"`
|
||||
- Zero build step: Node.js + Express + vanilla HTML/CSS/JS
|
||||
- `npm test` runs a real test suite (folder adapter, Immich adapter with a
|
||||
mocked API, and the HTTP API end-to-end) — no separate test server to stand up
|
||||
|
||||
## Quickstart
|
||||
|
||||
@@ -45,7 +58,7 @@ npm start
|
||||
```
|
||||
|
||||
Open `http://localhost:5757`. On first run you'll land on **Settings** —
|
||||
pick the "Local folder" adapter, point it at a folder, and start swiping.
|
||||
pick an adapter, fill in its settings, and start swiping.
|
||||
|
||||
Alternatively, copy `swipeanything.config.example.json` to
|
||||
`swipeanything.config.json` and edit it directly:
|
||||
@@ -54,22 +67,55 @@ Alternatively, copy `swipeanything.config.example.json` to
|
||||
cp swipeanything.config.example.json swipeanything.config.json
|
||||
```
|
||||
|
||||
### Running the tests
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
Uses Node's built-in test runner (`node --test`) — no extra dev dependencies.
|
||||
The Immich adapter is tested with a mocked `fetch`, so nothing here needs a
|
||||
live server.
|
||||
|
||||
## Controls
|
||||
|
||||
| Action | Gesture | Key | Button |
|
||||
|---|---|---|---|
|
||||
| Keep | Drag right | `→` | green circle |
|
||||
| Reject (moves to trash) | Drag left | `←` | red circle |
|
||||
| Skip (folder adapter) | — | `Space` | circle |
|
||||
| Undo | — | `Ctrl/Cmd+Z` | Undo |
|
||||
| Keep | Drag right | `→` | Keep → |
|
||||
| Reject (moves to trash) | Drag left | `←` | Reject ← |
|
||||
| Skip / next (no decision) | — | `↓` or `Space` | Skip ↓ |
|
||||
| Undo / go back | — | `↑` or `Ctrl/Cmd+Z` | Undo ↑ |
|
||||
| Shortcuts help | — | `Shift+?` | `?` in header |
|
||||
|
||||
Keep/Reject (button or key) flash the KEEP/REJECT stamp and fling the card, same as a drag.
|
||||
|
||||
## Adapters
|
||||
|
||||
### Local folder
|
||||
|
||||
Point at any local folder. Optionally recurse into subfolders and filter by
|
||||
extension. "Reject" moves the file into `.swipeanything-trash/` next to the
|
||||
source; "Empty trash" (a separate, confirm-guarded action shown in the header
|
||||
once there's anything to empty) permanently deletes what's in there.
|
||||
|
||||
### Immich
|
||||
|
||||
Point at a self-hosted [Immich](https://immich.app) server + API key and
|
||||
swipe through your photo library newest-first (or randomly). "Reject" moves
|
||||
the asset to Immich's own trash via the API; "Undo" restores it from there.
|
||||
This is an early adapter — tested against the documented API shape, not
|
||||
every Immich version, so please open an issue/PR if something doesn't match
|
||||
your server.
|
||||
|
||||
## How adapters work
|
||||
|
||||
Every adapter implements a small contract (`adapters/base.js`): declare a
|
||||
settings schema, list the items to review, and apply/undo actions on them.
|
||||
The bundled `adapters/folder.js` is the reference implementation — read it
|
||||
first, then see [CONTRIBUTING.md](CONTRIBUTING.md) for a full walkthrough of
|
||||
writing your own (email, database rows, RSS, anything).
|
||||
settings schema, list the items to review, apply/undo actions on them, and
|
||||
stream a preview/thumbnail. `adapters/folder.js` (local filesystem) and
|
||||
`adapters/immich.js` (remote API) are two different reference
|
||||
implementations — read whichever is closer to what you're building, then see
|
||||
[CONTRIBUTING.md](CONTRIBUTING.md) for a full walkthrough of writing your own
|
||||
(email, database rows, RSS, anything).
|
||||
|
||||
```
|
||||
UI (public/) --> Express API (server.js) --> Adapter Registry --> your adapter
|
||||
@@ -81,16 +127,34 @@ UI (public/) --> Express API (server.js) --> Adapter Registry --> your adapter
|
||||
server.js Express app: API + static file serving
|
||||
adapters/base.js Adapter contract every adapter implements
|
||||
adapters/folder.js Reference adapter: local files/folders
|
||||
adapters/immich.js Reference adapter: remote API (Immich)
|
||||
adapters/registry.js Adapter registration
|
||||
lib/thumbnails.js macOS Quick Look-based thumbnail cache
|
||||
public/ Vanilla HTML/CSS/JS swipe UI + settings UI
|
||||
test/ node:test suite (adapters + HTTP API)
|
||||
swipeanything.config.example.json Copy to swipeanything.config.json to configure
|
||||
```
|
||||
|
||||
## Notes on platform-specific features
|
||||
|
||||
A few conveniences use macOS system tools instead of adding npm dependencies,
|
||||
and quietly no-op elsewhere:
|
||||
|
||||
- **Thumbnails** use `qlmanage` (Quick Look). Falls back to serving the
|
||||
original file if generation fails or times out, or on non-macOS platforms.
|
||||
- **Folder picker** uses `osascript`/Finder. On other platforms, or if
|
||||
automation permission is denied, just type the path into the field instead.
|
||||
|
||||
## Roadmap ideas
|
||||
|
||||
- Email adapter (IMAP): swipe archive/delete/label
|
||||
- Generic JSON/CSV/database-row adapter: swipe to tag or update a status column
|
||||
- Multi-select "later" bucket as a first-class third action everywhere
|
||||
- Mobile-friendly PWA wrapper
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Reference in New Issue
Block a user