|
| 1 | +# visualizer-helper — Project Context |
| 2 | + |
| 3 | +## What this is |
| 4 | + |
| 5 | +A utility library consumed exclusively by views built on the [npellet/visualizer](https://github.com/npellet/visualizer) framework. Views run inside iframes; this library bridges visualizer's AMD module system with CouchDB (via rest-on-couch) and provides domain helpers for ELN, NMR, chemistry, biology, and spectroscopy. |
| 6 | + |
| 7 | +The library is **never published to npm** (`"private": true`). It is loaded at runtime by the visualizer's AMD loader (RequireJS-compatible). Source files live in `src/`, transpiled AMD output lands in `build/` via `npm run babel-test`. |
| 8 | + |
| 9 | +## Module system — two coexisting styles |
| 10 | + |
| 11 | +| Style | Used in | Import form | |
| 12 | +| ------------------------- | ------------------------------------------------------------- | ---------------------------------------- | |
| 13 | +| AMD (`define([...], fn)`) | `rest-on-couch/`, `util/`, `chemistry/`, `biology/`, `tiles/` | `define(['dep'], (dep) => { ... })` | |
| 14 | +| ESM (`import`/`export`) | `eln/`, newer files | `import Roc from '../rest-on-couch/Roc'` | |
| 15 | + |
| 16 | +When adding code, match the style of the surrounding directory. The Babel plugin `@zakodium/babel-plugin-transform-modules-amd` converts both to AMD for the browser build. |
| 17 | + |
| 18 | +## The `Roc` class (`rest-on-couch/Roc.js`) |
| 19 | + |
| 20 | +Central interface to a rest-on-couch server. Every view that talks to CouchDB instantiates one: |
| 21 | + |
| 22 | +```javascript |
| 23 | +const roc = new Roc({ url, database, kind, messages }); |
| 24 | +// url: base URL of rest-on-couch server (e.g. 'https://couch.example.com') |
| 25 | +// database: CouchDB database name (e.g. 'eln') |
| 26 | +// kind: default $kind for new entries (optional) |
| 27 | +``` |
| 28 | + |
| 29 | +Key methods: |
| 30 | + |
| 31 | +| Method | Description | |
| 32 | +| --------------------------------- | ----------------------------------------------------------------------------------------- | |
| 33 | +| `roc.getUser()` | Returns `{ username, name, groups, roles }` from the session | |
| 34 | +| `roc.getUserPrefs(defaultPrefs?)` | Loads per-user JSON blob from `/db/{db}/user/_me` | |
| 35 | +| `roc.setUserPrefs(prefs)` | Saves (merges on server) per-user JSON blob | |
| 36 | +| `roc.getUserInfo()` | Extended user metadata | |
| 37 | +| `roc.view(viewName, options)` | CouchDB view query; options: `key`, `startkey`, `endkey`, `limit`, `mine`, `include_docs` | |
| 38 | +| `roc.create(entry)` | Creates a CouchDB entry `{ $id, $content, $kind }` | |
| 39 | +| `roc.get(entry)` | Fetches full document | |
| 40 | +| `roc.getById(id)` | Fetches by `$id` | |
| 41 | +| `roc.update(entry)` | Updates existing document | |
| 42 | +| `roc.delete(entry)` | Deletes document | |
| 43 | +| `roc.document(uuid, opts)` | Tracked/reactive document; `opts.varName`, `opts.track` | |
| 44 | + |
| 45 | +Authentication is cookie/session-based. All requests use `credentials: 'include'`. |
| 46 | + |
| 47 | +## Saving and loading personal preferences in CouchDB |
| 48 | + |
| 49 | +Three mechanisms exist, from simplest to most powerful: |
| 50 | + |
| 51 | +### 1. Per-user global preferences — `getUserPrefs` / `setUserPrefs` |
| 52 | + |
| 53 | +Simplest option. A single JSON blob per user per database, stored at `/db/{database}/user/_me`. |
| 54 | + |
| 55 | +```javascript |
| 56 | +// Load (with fallback defaults) |
| 57 | +const prefs = await roc.getUserPrefs({ theme: 'light', pageSize: 25 }); |
| 58 | + |
| 59 | +// Save (keys are merged server-side, not replaced) |
| 60 | +await roc.setUserPrefs({ theme: 'dark' }); |
| 61 | +``` |
| 62 | + |
| 63 | +Use when: preferences are global to the user across all views of a database. |
| 64 | + |
| 65 | +### 2. Per-view, per-user preferences — `roc.UserViewPrefs` |
| 66 | + |
| 67 | +Stored as a CouchDB entry with `$kind: 'userViewPrefs'`, keyed by `[username, ['userViewPrefs', prefID]]` in the `entryByOwnerAndId` view. `prefID` defaults to the current view's `_id` if omitted. |
| 68 | + |
| 69 | +```javascript |
| 70 | +// Available on any Roc instance |
| 71 | +const prefs = await roc.UserViewPrefs.get(); // returns $content or undefined |
| 72 | +await roc.UserViewPrefs.set({ zoom: 2, filter: 'NMR' }); |
| 73 | + |
| 74 | +// Explicit ID (useful when one view manages multiple pref namespaces) |
| 75 | +const prefs = await roc.UserViewPrefs.get('myPrefKey'); |
| 76 | +await roc.UserViewPrefs.set(value, 'myPrefKey'); |
| 77 | +``` |
| 78 | + |
| 79 | +Use when: preferences are specific to a view and should not bleed across views. |
| 80 | + |
| 81 | +### 3. Generic tracked preferences — `preferencesFactory` (`eln/preference.js`) |
| 82 | + |
| 83 | +Creates a CouchDB entry with `$kind: 'viewPreferences'` and ID `${id}_${username}`. Supports reactive updates via `roc.document()` with `track: true`. |
| 84 | + |
| 85 | +```javascript |
| 86 | +import { preferencesFactory } from '../eln/preference'; |
| 87 | + |
| 88 | +const vp = await preferencesFactory('myView', { |
| 89 | + url, |
| 90 | + database: 'eln', |
| 91 | + initial: [{ key: 'columns', value: ['name', 'date'] }], |
| 92 | +}); |
| 93 | + |
| 94 | +// The preference object is also cached in the visualizer API under `options.name` |
| 95 | +await vp.save(); |
| 96 | +``` |
| 97 | + |
| 98 | +Use when: you need reactive two-way sync between the view's data model and CouchDB. |
| 99 | + |
| 100 | +### 4. localStorage only — `track` (`util/track.js`) |
| 101 | + |
| 102 | +Not CouchDB-backed. Persists in `localStorage` keyed by a cookie name. Supports versioning and default merging. Useful for ephemeral UI state that doesn't need to roam. |
| 103 | + |
| 104 | +```javascript |
| 105 | +// AMD |
| 106 | +define(['Track'], (track) => { |
| 107 | + track( |
| 108 | + 'massOptions', |
| 109 | + { version: 1, normalize: false }, |
| 110 | + { varName: 'massOptions' }, |
| 111 | + ).then((result) => { |
| 112 | + /* result is a reactive data object */ |
| 113 | + }); |
| 114 | +}); |
| 115 | +``` |
| 116 | + |
| 117 | +## Directory guide |
| 118 | + |
| 119 | +``` |
| 120 | +rest-on-couch/ Roc.js, UserViewPrefs.js, UserAnalysisResults.js |
| 121 | +eln/ ELN-specific helpers (samples, NMR, preference.js, stock, …) |
| 122 | +eln/util/ Shared ELN utilities (appendedDragAndDrop, getChartFromMass, …) |
| 123 | +util/ Generic helpers: track.js, ModulePrefsManager.js, tips.js, privacy.js |
| 124 | +chemistry/ Molecular formula, OCL, NMR renderers |
| 125 | +biology/ DNA sequences, alignment helpers |
| 126 | +spectra-data/ Spectroscopy data structures |
| 127 | +tiles/ UI tile/grid management |
| 128 | +on-tabs/ Multi-tab iframe bridge utilities |
| 129 | +build/ Babel-transpiled AMD output (generated — do not edit) |
| 130 | +``` |
| 131 | + |
| 132 | +## Build |
| 133 | + |
| 134 | +```bash |
| 135 | +npm run babel-test # transpile src → build/ (AMD) |
| 136 | +``` |
| 137 | + |
| 138 | +No test suite currently. Linting: `npm run eslint`. Formatting: `npm run prettier-write`. |
| 139 | + |
| 140 | +## Visualizer integration |
| 141 | + |
| 142 | +Views load modules via AMD paths configured in the visualizer. The library exposes helpers that interact with: |
| 143 | + |
| 144 | +- `src/util/api` — visualizer's data/variable API (`API.createData`, `API.getData`, `API.cache`) |
| 145 | +- `src/util/versioning` — change tracking (`Versioning.getData().onChange(...)`) |
| 146 | +- `IframeBridge` — postMessage bridge between iframe and parent tab (`tab.status`, config passing) |
0 commit comments