A camera-based QR scanner for an RO-Crate catalogue and register, built as a plain HTML/CSS/JS site. No npm install, no bundler, no TypeScript: just static files served as-is, using native ES modules.
- Check RO-Crate validity
- Use real cat IDs
- Use real cat properties
The app is hosted on GitHub Pages, so you can use it without running anything locally:
https://language-research-technology.github.io/collection-scanner/
Scan this QR code with your phone's camera to open it directly:
Want to try the scanner itself without printing anything? Sample QR codes are hosted alongside the app — open that page on one device and scan the codes on it from another.
- Scan a QR code with the device camera. It's looked up by
@idin the register: found and already promoted → opens the linked catalogue entry; found, not yet promoted → opens that register entry; not found at all → creates a new register entry pre-filled with the scanned code. - Browse toggles between the catalogue and the register.
- Promote to catalogue gives the new catalogue entry its own freshly-generated
@id(crypto.randomUUID()), linked back to the register entry viacustom:registerId. Every catalogue@idis meant to have a register entry referencing it this way — catalogue entries can only be created by promoting a register entry, and a register entry can't be deleted once it's linked. - Data lives in the browser's IndexedDB (hand-rolled wrapper, no
idb-keyval) and round-trips to realro-crate-metadata.jsonfiles via the Export/Import buttons.
See data/ for sample crates and QR codes, and the top-level README there for more on the data model.
A code encodes nothing but a plain-text @id string — no URL, no JSON, just the exact value the scanner matches against the register (e.g. #reg-2023-014). Any QR generator works; for example, with qrencode:
qrencode -o my-code.png -s 10 -m 2 "#reg-2023-014"- Code for an existing item — encode that register entry's
@idvalue exactly as it appears inro-crate-metadata.json, including the leading#. - Code for a new item — pick any
@idstring not already used in the register (following the same#-prefixed convention) and encode it. Scanning it won't match anything, so the app opens the "create new register entry" flow pre-filled with that id — see#new-001/#new-002in data/codes/ for examples.
| file | description |
|---|---|
| index.html | entry point, loads styles.css and src/main.js |
| styles.css | all styles |
| src/ | source modules |
| src/main.js | bootstraps the app |
| src/app.js | state + render orchestration |
| src/dom.js | tiny hyperscript-style element builder — the only "framework" here |
| src/rocrate.js | RO-Crate helpers (crate model, CRUD, id generation) |
| src/storage.js | raw IndexedDB wrapper |
| src/fileIO.js | export (download) / import (file picker) of ro-crate-metadata.json |
| src/scanner.js | camera QR scanning, wraps the vendored qr-scanner library |
| src/listView.js | list screen builder |
| src/catalogueForm.js | catalogue entry form builder |
| src/registerForm.js | register entry form builder |
| vendor/ | vendored libraries |
| vendor/qr-scanner.min.js + vendor/qr-scanner-worker.min.js | vendored from the qr-scanner npm package (ESM build, MIT licensed — see qr-scanner-LICENSE) |
Camera access requires a secure context (HTTPS or localhost) and ES module <script> tags require a real HTTP origin — you can't just double-click index.html. Serve the folder with any static file server, for example:
python3 -m http.server 4400or
npx serve .then open http://localhost:4400 (or whatever port/tool you used).
You need HTTPS to test camera access from another device on your network. Any static-server option with a --ssl/HTTPS flag works, or put a tool like mkcert-generated certs in front of the plain server.
Quickest path, using local-web-server (no install required):
- From this folder, start an HTTPS static server:
This generates a self-signed certificate automatically.
npx local-web-server --https --port 4400
- On the phone, open
https://<your-ip>:4400(e.g.https://192.168.1.64:4400). - Accept the certificate warning — it's self-signed, so the browser will flag it as untrusted:
- Chrome/Android: Advanced → Proceed
- Safari/iOS: Show Details → visit this website
- Allow camera access when prompted.
To avoid the warning entirely, generate a locally-trusted cert with mkcert (brew install mkcert) and point your static server at it instead.
