pict-section-fileview 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/package.json +61 -5
- package/source/BuiltinRenderers.js +1132 -0
- package/source/Fileview-Probe.js +233 -0
- package/source/Pict-Fileview-CSS.js +104 -0
- package/source/Pict-Section-Fileview.js +99 -0
- package/source/providers/Pict-Fileview-Provider.js +834 -0
- package/source/views/Pict-View-Fileview.js +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Steven Velozo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,196 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pict-section-fileview
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A universal, embeddable **quality file viewer** for the Retold/Pict ecosystem. Drop one control on a page, point it
|
|
4
|
+
at a server-served file — a [Sluice](https://github.com/fable-retold) StoredFile record *or* a bare, range-served URL
|
|
5
|
+
— and it detects the type and renders it well on phones, iPads, desktops, TVs, and projected secondary displays.
|
|
6
|
+
|
|
7
|
+
It is an **orchestrator, not a renderer.** It owns type detection, a pluggable kind→renderer registry, the viewer
|
|
8
|
+
chrome, and child lifecycle, and delegates each media kind to a purpose-built child section that stays independently
|
|
9
|
+
usable on any page.
|
|
10
|
+
|
|
11
|
+
> **Status: Phase 1 complete.** Shipped: the dispatch core (registry, descriptor normalization, score-tier
|
|
12
|
+
> resolution, the chrome), the **probe ladder** (HEAD → Range → magic-byte sniff for bare URLs), and the full
|
|
13
|
+
> renderer set — the always-matches **download card**, a self-contained **text** lens, a **code** lens delegating to
|
|
14
|
+
> `pict-section-code`, a **markdown** lens delegating to `pict-section-content`, the **image** lenses (a
|
|
15
|
+
> self-contained responsive `<img>` plus a richer zoom/pan lens delegating to `pict-section-image`), the **video**
|
|
16
|
+
> and **audio** lenses (a self-contained native `<video>`/`<audio controls>` plus bespoke players delegating to
|
|
17
|
+
> `pict-section-video` / `pict-section-audio`), a **PDF** lens (PDF.js, page-lazy, Range-fed), and a **deep-zoom**
|
|
18
|
+
> lens for enormous images (OpenSeaDragon on a server DZI tile pyramid). See the architecture plan in the
|
|
19
|
+
> `pict-section-fileview` plansheet.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install pict-section-fileview
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Hard dependencies are only `pict-view` and `pict-provider`. Every specialized renderer is **optional** and
|
|
28
|
+
host-provided: `pict-section-code` (code), `pict-section-content` (markdown), `pict-section-image` (zoom/pan image),
|
|
29
|
+
`pict-section-video` (video), `pict-section-audio` (audio), `PDF.js` (`window.pdfjsLib`, for PDFs), and
|
|
30
|
+
`OpenSeaDragon` (`window.OpenSeadragon`, for deep zoom). A renderer whose dependency is absent abstains via its
|
|
31
|
+
`Capability` gate, and the viewer falls through to the next best renderer — code/markdown degrade to readable text,
|
|
32
|
+
an image to a plain `<img>`, a video/audio to the native `<video>`/`<audio controls>`, and only a genuinely opaque
|
|
33
|
+
or non-web-playable file reaches the download card — rather than breaking.
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
const libFileview = require('pict-section-fileview');
|
|
39
|
+
|
|
40
|
+
// Register a provider + view pair with your pict application.
|
|
41
|
+
libFileview.createFileview(pict, { Identifier: 'MyFileView', DestinationAddress: '#SomeDiv' });
|
|
42
|
+
|
|
43
|
+
// Render the chrome, then point it at a file.
|
|
44
|
+
pict.views.MyFileView.render();
|
|
45
|
+
|
|
46
|
+
pict.providers.MyFileView.setFile('https://host/1.0/Depot/File/42/Open'); // a bare URL
|
|
47
|
+
pict.providers.MyFileView.setFile(tmpStoredFileRecord); // or a record
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`createFileview(pPict, pConfig)` config:
|
|
51
|
+
|
|
52
|
+
| Key | Meaning |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `Identifier` | The instance hash — the registration key for the provider, the view, and every element id (so multiple fileviews coexist on one page). Default `Pict-Section-Fileview`. |
|
|
55
|
+
| `DestinationAddress` | CSS selector the viewer mounts into. Default `#Pict-Application-Container` (override app-wide with `setDefaultDestination()`). |
|
|
56
|
+
| `File` | An optional initial file (record or URL). |
|
|
57
|
+
| `Renderers` | Extra renderer plugins to register on top of the built-ins. |
|
|
58
|
+
| `DisableBuiltins` | Skip seeding the built-in renderers (you provide your own set). |
|
|
59
|
+
| `Fetch` | A `fetch`-compatible function the viewer uses to probe and load file bytes. Defaults to the page global; set it to route through a host REST client or to stub in tests. |
|
|
60
|
+
| `Probe` | `false` disables the bare-URL probe ladder (HEAD/Range/magic). Default on. |
|
|
61
|
+
| `ShowFullscreen` | `false` hides the toolbar's maximize button. Default on — the viewer can fill the browser viewport and back (`toggleFullscreen()` / `Esc`). |
|
|
62
|
+
| `CodeSectionClass` | The `pict-section-code` class for the code lens. Defaults to `window.PictSectionCode`. Absent ⇒ code files degrade to the text lens. |
|
|
63
|
+
| `ContentProviderClass` | The `pict-section-content` provider class for the markdown lens. Defaults to `window.PictSectionContent.PictContentProvider`. Absent ⇒ markdown degrades to the text lens. |
|
|
64
|
+
| `ImageSectionClass` | The `pict-section-image` class for the zoom/pan image lens. Defaults to `window.PictSectionImage`. Absent ⇒ images render in the self-contained basic `<img>` lens. |
|
|
65
|
+
| `VideoSectionClass` | The `pict-section-video` class for the bespoke video lens. Defaults to `window.PictSectionVideo`. Absent ⇒ video renders in the self-contained native `<video controls>` lens. |
|
|
66
|
+
| `AudioSectionClass` | The `pict-section-audio` class for the bespoke audio lens. Defaults to `window.PictSectionAudio`. Absent ⇒ audio renders in the self-contained native `<audio controls>` lens. |
|
|
67
|
+
| `PdfLibrary` | The PDF.js library for the PDF lens. Defaults to `window.pdfjsLib`. Absent ⇒ a PDF degrades to the download card. |
|
|
68
|
+
| `PdfWorkerSrc` | A PDF.js worker URL to set once (when PDF.js has not been given one). Alternatively set `pdfjsLib.GlobalWorkerOptions.workerSrc` yourself. |
|
|
69
|
+
| `OpenSeadragon` | The OpenSeaDragon factory for the deep-zoom lens. Defaults to `window.OpenSeadragon`. Absent ⇒ an enormous image degrades to the download card. |
|
|
70
|
+
| `OpenSeadragonOptions` | Extra options merged into the OpenSeaDragon viewer settings (e.g. `{ showNavigator: true }`). |
|
|
71
|
+
| `CodeMaxBytes` | Byte cap fed to the code highlighter (default 2 MB); larger files use the text lens. |
|
|
72
|
+
| `TextMaxBytes` | Byte cap (a Range read) for the text / markdown lenses (default 5 MB); the remainder is a download. |
|
|
73
|
+
| `CSSExtra` | Extra CSS appended to the shell styles. |
|
|
74
|
+
| `Ordinal`, `ProviderClass`, `ViewClass` | Advanced overrides. |
|
|
75
|
+
|
|
76
|
+
## Built-in renderers
|
|
77
|
+
|
|
78
|
+
| Hash | Handles | Depends on |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `Fileview-Renderer-Code` | source files (CodeJar, read-only, line numbers) | `pict-section-code` + a `window.CodeJar` page global |
|
|
81
|
+
| `Fileview-Renderer-Markdown` | `.md` / markdown | `pict-section-content`'s `parseMarkdown` |
|
|
82
|
+
| `Fileview-Renderer-DeepZoom` | enormous images, via a server DZI tile pyramid (zoom / pan) | OpenSeaDragon + a `Meta.TileSources` |
|
|
83
|
+
| `Fileview-Renderer-PDF` | PDF documents; pages rendered to canvas, page-lazy, Range-fed | PDF.js |
|
|
84
|
+
| `Fileview-Renderer-Image` | images, with zoom / pan / download chrome | `pict-section-image` |
|
|
85
|
+
| `Fileview-Renderer-Video` | web-playable video, with a bespoke control bar (play / seek / volume / rate) | `pict-section-video` |
|
|
86
|
+
| `Fileview-Renderer-Audio` | web-playable audio, a titled player with an optional waveform | `pict-section-audio` |
|
|
87
|
+
| `Fileview-Renderer-ImageBasic` | images; a responsive, fit-to-stage `<img>` | nothing (self-contained) |
|
|
88
|
+
| `Fileview-Renderer-VideoBasic` | web-playable video; a native `<video controls>` | nothing (self-contained) |
|
|
89
|
+
| `Fileview-Renderer-AudioBasic` | web-playable audio; a native `<audio controls>` | nothing (self-contained) |
|
|
90
|
+
| `Fileview-Renderer-Text` | any textual file; a `<pre>` | nothing (self-contained) |
|
|
91
|
+
| `Fileview-Renderer-DownloadCard` | everything else (the floor) | nothing |
|
|
92
|
+
|
|
93
|
+
The delegating lenses are **optional and host-provided** (see the config table): each gates on its dependency via
|
|
94
|
+
its `Capability`, and when that dependency is absent the viewer falls through to the next best renderer — a code or
|
|
95
|
+
markdown file without its lens still renders as readable text, an image without `pict-section-image` still renders
|
|
96
|
+
in the basic `<img>` lens, and only a genuinely opaque binary reaches the download card. The text/code/markdown
|
|
97
|
+
lenses fetch the file's bytes themselves (client-only, Range-capped) through the configured `Fetch`; the image
|
|
98
|
+
lenses load the bytes directly through an `<img>`.
|
|
99
|
+
|
|
100
|
+
An image a browser cannot paint (TIFF, HEIC, camera RAW) and an **oversize** image (raw bytes larger than ~30 MB —
|
|
101
|
+
never blit into an `<img>`) score 0 for the ordinary image lenses. When the descriptor carries a `Meta.TileSources`
|
|
102
|
+
(a DZI descriptor URL or an OpenSeaDragon tile source) **and** OpenSeaDragon is on the page, the **deep-zoom** lens
|
|
103
|
+
takes over and streams the image from a server tile pyramid — the client never holds the whole image. Produce that
|
|
104
|
+
pyramid with any DZI tiler; [`orator-file-service`](https://github.com/fable-retold/orator-file-service) generates
|
|
105
|
+
one with libvips (`dzsave`) and serves it (and the original bytes, with Range support) over HTTP. The deep-zoom lens
|
|
106
|
+
is self-contained — it news up OpenSeaDragon on its own instance-unique element, so two deep-zoom fileviews coexist
|
|
107
|
+
on one page. Without a tile source (or without OpenSeaDragon) an oversize/unpaintable image still degrades to the
|
|
108
|
+
download card.
|
|
109
|
+
|
|
110
|
+
The **PDF** lens delegates to PDF.js (a host-provided page global, `window.pdfjsLib`): it hands PDF.js the URL and
|
|
111
|
+
lets it fetch pages on demand with HTTP Range requests, rendering each page to a `<canvas>` lazily as it scrolls
|
|
112
|
+
into view (eagerly where `IntersectionObserver` is unavailable). Without PDF.js a PDF degrades to the download card.
|
|
113
|
+
|
|
114
|
+
The **video** and **audio** lenses play web-playable media: MP4/WebM/Ogg for video, MP3/M4A/Ogg/WAV/FLAC for audio.
|
|
115
|
+
Seeking rides the browser's native HTTP Range requests, so a range-served file scrubs without downloading the whole
|
|
116
|
+
clip — the same 206 contract the rest of the viewer speaks. When `pict-section-video` / `pict-section-audio` are on
|
|
117
|
+
the page the richer bespoke players take over (a control bar with play/seek/volume/rate, a titled audio header with
|
|
118
|
+
an optional `Meta.peaks` waveform); absent, the self-contained native `<video controls>` / `<audio controls>` lens
|
|
119
|
+
plays it anyway. A container a browser cannot decode (AVI, MKV, WMV; WMA, AIFF) scores 0 for the media lenses and
|
|
120
|
+
degrades to the download card — a server-side transcode into a web format is a later slice, like the deep-zoom tiler.
|
|
121
|
+
|
|
122
|
+
## Renderer contract
|
|
123
|
+
|
|
124
|
+
A renderer is a plain object the registry dispatches to:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
{
|
|
128
|
+
Hash: 'My-Renderer', // unique id
|
|
129
|
+
Priority: 0, // tiebreak when two renderers score equally (higher wins)
|
|
130
|
+
Capability: (provider) => boolean, // optional — abstain (fall through) when a page-global lib is absent
|
|
131
|
+
matches: (descriptor) => number, // score tier (see below)
|
|
132
|
+
prepareDescriptor: (descriptor, provider) => void, // optional — enrich before mount (the probe hook)
|
|
133
|
+
mount: (containerSelector, descriptor, provider) => handle
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`matches()` returns a **score tier**, and `resolveRenderer()` picks the highest (ties broken by `Priority`, then
|
|
138
|
+
registration order):
|
|
139
|
+
|
|
140
|
+
| Signal | Score |
|
|
141
|
+
|---|---|
|
|
142
|
+
| explicit config `Kind` | 10000 |
|
|
143
|
+
| strong extension | ~700 |
|
|
144
|
+
| header `Content-Type` | ~400 |
|
|
145
|
+
| magic bytes | ~200 |
|
|
146
|
+
| download-card fallback | 1 (always) |
|
|
147
|
+
|
|
148
|
+
`mount()` paints into `containerSelector` (always through `pict.ContentAssignment`) and returns a handle
|
|
149
|
+
`{ Hash, Descriptor, ChildViewHash?, Ready?, update?(descriptor), destroy() }`. The provider keeps one handle at a
|
|
150
|
+
time. On a **same-kind re-point** (the next file resolves to the same renderer) it calls the handle's optional
|
|
151
|
+
`update(descriptor)` to re-feed the live child in place rather than tearing it down and re-mounting — the
|
|
152
|
+
*reuse-one-per-kind* rule, since Pict has no `removeView` and re-minting a child per file leaks views/observers. On a
|
|
153
|
+
**type switch** it calls `destroy()`. `Ready` is an optional promise an async renderer resolves once its content has
|
|
154
|
+
painted (handy in tests). Register your own with `provider.registerRenderer(plugin)` — new types need zero changes to
|
|
155
|
+
the core.
|
|
156
|
+
|
|
157
|
+
### FileDescriptor
|
|
158
|
+
|
|
159
|
+
The normalized contract every renderer reads:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
{ URL, Name, Extension, MIMEType, Size, Kind, AcceptRanges, MagicBytes,
|
|
163
|
+
Meta: { title, language, poster, TileSources, duration, peaks },
|
|
164
|
+
Probe: { KindSource, MIMESource, ResolvedRendererHash, Strategy } }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`normalizeDescriptor()` accepts either a bare URL string (`Strategy: 'auto'`) or a record (a Sluice StoredFile or a
|
|
168
|
+
pre-built descriptor; `Strategy: 'none'` when `Kind` is already set), and both become one FileDescriptor.
|
|
169
|
+
|
|
170
|
+
## Examples
|
|
171
|
+
|
|
172
|
+
Two runnable example applications live under `example_applications/`:
|
|
173
|
+
|
|
174
|
+
| Example | What it shows |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `fileview_gallery` | Ten independent fileview instances on one page, one per media type — text, JavaScript (code lens), markdown, a raster photo, a vector SVG, a binary that degrades to the download card, a **PDF** (PDF.js), an **enormous image** deep-zoomed with OpenSeaDragon against a live `orator-file-service` DZI pyramid, a **video** (pict-section-video), and an **audio** track (pict-section-audio). The breadth-and-multi-instance showcase. |
|
|
177
|
+
| `fileview_browser` | A single fileview embedded in an app shell and driven by a file list — clicking a file re-points the viewer. Demonstrates live type dispatch, the *reuse-one-per-kind* `update()` path (image→image), and `destroy()` teardown on a type switch (image→code), with the active lens shown under the viewer. |
|
|
178
|
+
|
|
179
|
+
Both drive the **real** delegated sections (pict-section-code + CodeJar, pict-section-content, pict-section-image, pict-section-video, pict-section-audio) — nothing is faked.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm run build # build the module
|
|
183
|
+
cd example_applications/fileview_gallery && npm run build # build an example's browser bundle
|
|
184
|
+
npm run example-serve # (from the module root) serve the built examples at http://localhost:<port>/
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`example-serve` runs `quack examples-serve` in static mode; open `/<example>/dist/index.html`.
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npm test # Mocha TDD via quackage
|
|
193
|
+
npm run build # browser bundle via quackage
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
MIT © Steven Velozo
|
package/package.json
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "pict-section-fileview",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A universal, embeddable file viewer: one control that detects a file's type (from a Sluice record or a bare range-served URL) and dispatches it to the right renderer — code, markdown, images (ordinary + enormous), PDF, video and audio — delegating each media kind to a purpose-built child section.",
|
|
5
|
+
"main": "source/Pict-Section-Fileview.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "npx quack test",
|
|
8
|
+
"tests": "npx quack test -g",
|
|
9
|
+
"start": "node source/Pict-Section-Fileview.js",
|
|
10
|
+
"coverage": "npx quack coverage",
|
|
11
|
+
"build": "quack build",
|
|
12
|
+
"example": "npx quack examples",
|
|
13
|
+
"example-serve": "npx quack examples-serve",
|
|
14
|
+
"types": "tsc -p ."
|
|
15
|
+
},
|
|
16
|
+
"quackageExamples": {
|
|
17
|
+
"mode": "static"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"source"
|
|
21
|
+
],
|
|
22
|
+
"directories": {
|
|
23
|
+
"test": "test"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/fable-retold/pict-section-fileview.git"
|
|
28
|
+
},
|
|
29
|
+
"author": "steven velozo <steven@velozo.com>",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/fable-retold/pict-section-fileview/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/fable-retold/pict-section-fileview#readme",
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"browser-env": "^3.3.0",
|
|
37
|
+
"chai": "^4.3.0",
|
|
38
|
+
"pict": "^1.0.372",
|
|
39
|
+
"pict-application": "^1.0.34",
|
|
40
|
+
"quackage": "^1.3.0",
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
},
|
|
43
|
+
"mocha": {
|
|
44
|
+
"diff": true,
|
|
45
|
+
"extension": [
|
|
46
|
+
"js"
|
|
47
|
+
],
|
|
48
|
+
"package": "./package.json",
|
|
49
|
+
"reporter": "spec",
|
|
50
|
+
"slow": "75",
|
|
51
|
+
"timeout": "5000",
|
|
52
|
+
"ui": "tdd",
|
|
53
|
+
"watch-files": [
|
|
54
|
+
"source/**/*.js",
|
|
55
|
+
"test/**/*.js"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"pict-provider": "^1.0.13",
|
|
60
|
+
"pict-view": "^1.0.68"
|
|
61
|
+
}
|
|
62
|
+
}
|