@quario/viewer 0.2.0 → 0.4.0
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/CHANGELOG.md +57 -0
- package/README.md +76 -70
- package/lib/check.js +18 -59
- package/lib/chrome.js +3 -4
- package/lib/index.d.ts +34 -26
- package/lib/index.js +57 -38
- package/lib/menu.js +3 -3
- package/lib/stage.js +272 -234
- package/package.json +5 -7
- package/lib/mark.js +0 -40
- package/lib/style.js +0 -107
package/lib/index.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @quario/viewer — the embeddable viewer element. Not a render target: it
|
|
3
3
|
* compiles nothing and never touches the event stream. `<quario-viewer>`
|
|
4
|
-
* consumes a compiled report plus the host's configured targets
|
|
5
|
-
* properties,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* consumes a compiled report plus the host's configured export targets
|
|
5
|
+
* through properties, lays the report out on pages through `@quario/layout`
|
|
6
|
+
* and paints them on a shadow-DOM sheet — the same list the pdf target
|
|
7
|
+
* writes, so the preview breaks its pages where the document does
|
|
8
|
+
* (docs/adr/0039) — and offers export downloads for whichever exportable
|
|
9
|
+
* targets the host passed. Outcomes reach the host as `rendered` and `error`
|
|
10
|
+
* events, plus `renderComplete`, the one awaitable for the newest render
|
|
11
|
+
* settling.
|
|
9
12
|
*
|
|
10
13
|
* Importing this module defines nothing: the element class is the export,
|
|
11
14
|
* and `@quario/viewer/register` performs the one-line define for hosts that
|
|
12
15
|
* want it (docs/adr/0005-the-surfaces-are-custom-elements.md).
|
|
13
16
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* docs/adr/0017-the-viewer-watermarks-the-sheet.md.
|
|
17
|
+
* Nothing here is markup: the sheet is a stack of canvases, and the list's
|
|
18
|
+
* text reaches them as strings the painter draws, so the viewer has no
|
|
19
|
+
* markup edge and adds no report content of its own. The unlicensed marking
|
|
20
|
+
* rides on the list, one op per page, painted last — the same placement the
|
|
21
|
+
* pdf target writes (docs/adr/0017-the-viewer-watermarks-the-sheet.md).
|
|
20
22
|
*
|
|
21
23
|
* The error panel is the only wording the viewer authors, and it is not
|
|
22
24
|
* report content: it names which failure occurred, because no error knows
|
|
@@ -24,13 +26,13 @@
|
|
|
24
26
|
*/
|
|
25
27
|
import { Task, TaskStatus } from "@lit/task";
|
|
26
28
|
import { LitElement, html } from "lit";
|
|
29
|
+
import { layout } from "@quario/layout";
|
|
27
30
|
import { BUTTON } from "./button.js";
|
|
28
31
|
import { CHROME, progress } from "./chrome.js";
|
|
29
|
-
import {
|
|
32
|
+
import { exports, geometry, level, name, scheme } from "./check.js";
|
|
30
33
|
import { MENU, zoomMenu } from "./menu.js";
|
|
31
34
|
import { PANEL, label, panel } from "./panel.js";
|
|
32
35
|
import { SURFACE, stage } from "./stage.js";
|
|
33
|
-
import { REPORT } from "./style.js";
|
|
34
36
|
import { EXPORTS, download, exportGroup } from "./toolbar.js";
|
|
35
37
|
import { wanted } from "./zoom.js";
|
|
36
38
|
|
|
@@ -45,18 +47,19 @@ let said = (error) => {
|
|
|
45
47
|
|
|
46
48
|
// The properties are described once, in the hand-written public declarations,
|
|
47
49
|
// and read back here — a second copy in JSDoc is a copy that drifts.
|
|
48
|
-
/** @import { ViewableReport, ViewerPage } from './index.d.ts' */
|
|
50
|
+
/** @import { ViewableReport, ViewerFonts, ViewerPage } from './index.d.ts' */
|
|
49
51
|
|
|
50
52
|
/** The tag `@quario/viewer/register` defines the element under. */
|
|
51
53
|
export const TAG = "quario-viewer";
|
|
52
54
|
|
|
53
55
|
/**
|
|
54
56
|
* The viewer element. Hosts assign `report`, `targets` and `data` (plus the
|
|
55
|
-
* option properties `zoom`, `page`, `filename` and `colorScheme`)
|
|
56
|
-
* and `error`; rapid successive writes render only
|
|
57
|
-
* the render pipeline is a task keyed on those
|
|
58
|
-
* call-id guard drops every superseded run
|
|
59
|
-
* which is reported to no one (ADR 0005's
|
|
57
|
+
* option properties `zoom`, `page`, `fonts`, `filename` and `colorScheme`)
|
|
58
|
+
* and listen for `rendered` and `error`; rapid successive writes render only
|
|
59
|
+
* the newest state, because the render pipeline is a task keyed on those
|
|
60
|
+
* properties and the task's own call-id guard drops every superseded run
|
|
61
|
+
* whole — including its failure, which is reported to no one (ADR 0005's
|
|
62
|
+
* narrowed guarantee).
|
|
60
63
|
*
|
|
61
64
|
* Removal is not destruction: disconnecting abandons in-flight work and
|
|
62
65
|
* releases the resize observer, the properties persist, and reconnecting
|
|
@@ -66,7 +69,7 @@ export class QuarioViewer extends LitElement {
|
|
|
66
69
|
// CHROME declares the default palette every other sheet paints from, so it
|
|
67
70
|
// has to be in this list -- but anywhere in it: a custom property resolves
|
|
68
71
|
// down the inherited chain, not by stylesheet order.
|
|
69
|
-
static styles = [CHROME, BUTTON, MENU, SURFACE, PANEL
|
|
72
|
+
static styles = [CHROME, BUTTON, MENU, SURFACE, PANEL];
|
|
70
73
|
|
|
71
74
|
// Properties only, no attributes: `report`, `targets`, `data` and `page`
|
|
72
75
|
// are values no attribute could carry, and one door beats two.
|
|
@@ -76,6 +79,7 @@ export class QuarioViewer extends LitElement {
|
|
|
76
79
|
data: { attribute: false },
|
|
77
80
|
zoom: { attribute: false },
|
|
78
81
|
page: { attribute: false },
|
|
82
|
+
fonts: { attribute: false },
|
|
79
83
|
filename: { attribute: false },
|
|
80
84
|
colorScheme: { attribute: false },
|
|
81
85
|
};
|
|
@@ -108,37 +112,49 @@ export class QuarioViewer extends LitElement {
|
|
|
108
112
|
#exporting = new Set();
|
|
109
113
|
/** @type {(() => void) | undefined} */
|
|
110
114
|
#unwatch;
|
|
115
|
+
/** @type {Promise<void>} The newest swap's paint, which `renderComplete` waits behind. */
|
|
116
|
+
#painting = Promise.resolve();
|
|
111
117
|
|
|
112
118
|
// The whole async pipeline: keyed on the render properties, re-run when one
|
|
113
|
-
// changes
|
|
119
|
+
// changes — `page` and `fonts` among them, since either moves where the
|
|
120
|
+
// pages break. The task's call-id guard is the latest-wins machinery; side
|
|
114
121
|
// effects — the stage swap and both events — live only in the callbacks
|
|
115
122
|
// below, which that guard restricts to the newest run, never in the task
|
|
116
123
|
// body a stale run still executes to completion. `null` is the "nothing to
|
|
117
124
|
// show" result: unlike the task primitive's own initial-state symbol it
|
|
118
125
|
// settles `taskComplete`, which is what lets `renderComplete` always answer.
|
|
119
126
|
#task = new Task(this, {
|
|
120
|
-
args: () => [this.report, this.targets, this.data, this.#epoch],
|
|
121
|
-
task: async ([report, targets, data], { signal }) => {
|
|
127
|
+
args: () => [this.report, this.targets, this.data, this.page, this.fonts, this.#epoch],
|
|
128
|
+
task: async ([report, targets, data, page, fonts], { signal }) => {
|
|
122
129
|
if (!this.#begin(report, targets)) return null;
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
// The sheet's own target: the layout list, on the host's page and
|
|
131
|
+
// fonts — the same two a host passes to `pdf()`, which is what makes
|
|
132
|
+
// the preview page where the document pages.
|
|
133
|
+
let target = layout({ page: /** @type {any} */ (page), fonts: /** @type {any} */ (fonts) });
|
|
134
|
+
let list = await /** @type {any} */ (report).render(target, data);
|
|
125
135
|
// The engine takes no signal, so abandonment is the guards around this
|
|
126
136
|
// body; the check only spares the swap when the answer arrives after a
|
|
127
137
|
// disconnect mid-render.
|
|
128
138
|
if (this.#drop(signal)) return this.#abandon();
|
|
129
|
-
return
|
|
139
|
+
return { list, fonts };
|
|
130
140
|
},
|
|
131
|
-
onComplete: (
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
this.#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
onComplete: (result) => {
|
|
142
|
+
if (result === null || !this.isConnected) return;
|
|
143
|
+
// The swap sizes every page before it settles, so the reader's place
|
|
144
|
+
// is held; the paint it awaits is what `rendered` waits for.
|
|
145
|
+
this.#painting = this.#stage.swap(result.list, result.fonts).then(() => {
|
|
146
|
+
this.#landed = true;
|
|
147
|
+
this.#stale = false;
|
|
148
|
+
// A render that reached the sheet takes the panel down: the panel
|
|
149
|
+
// says what is wrong with what the reader is looking at, and this is
|
|
150
|
+
// the moment that stops being true. A successful export is not that
|
|
151
|
+
// moment.
|
|
152
|
+
this.#failure = undefined;
|
|
153
|
+
this.#dismissed = false;
|
|
154
|
+
// After the paint, so the update the task queued has already run.
|
|
155
|
+
this.requestUpdate();
|
|
156
|
+
this.dispatchEvent(new CustomEvent("rendered"));
|
|
157
|
+
});
|
|
142
158
|
},
|
|
143
159
|
onError: (error) => {
|
|
144
160
|
if (!this.isConnected) return;
|
|
@@ -161,6 +177,8 @@ export class QuarioViewer extends LitElement {
|
|
|
161
177
|
this.zoom = undefined;
|
|
162
178
|
/** @type {ViewerPage | undefined} */
|
|
163
179
|
this.page = undefined;
|
|
180
|
+
/** @type {ViewerFonts | undefined} */
|
|
181
|
+
this.fonts = undefined;
|
|
164
182
|
/** @type {string | undefined} */
|
|
165
183
|
this.filename = undefined;
|
|
166
184
|
/** @type {"light" | "dark" | "auto" | undefined} */
|
|
@@ -180,7 +198,7 @@ export class QuarioViewer extends LitElement {
|
|
|
180
198
|
this.#task.status === TaskStatus.INITIAL
|
|
181
199
|
? this.#landed
|
|
182
200
|
: this.#task.taskComplete.then(
|
|
183
|
-
() => this.#landed,
|
|
201
|
+
() => this.#painting.then(() => this.#landed),
|
|
184
202
|
() => false,
|
|
185
203
|
),
|
|
186
204
|
);
|
|
@@ -273,6 +291,7 @@ export class QuarioViewer extends LitElement {
|
|
|
273
291
|
if (this.#invalid) throw this.#invalid;
|
|
274
292
|
if (vacant(report, targets)) return null;
|
|
275
293
|
if (!this.isConnected) return this.#abandon();
|
|
294
|
+
exports(report, targets);
|
|
276
295
|
return true;
|
|
277
296
|
}
|
|
278
297
|
|
package/lib/menu.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The zoom menu: the magnifier trigger in the bar and the popover it opens —
|
|
3
|
-
* "Fit
|
|
3
|
+
* "Fit page" above a separator, then the percentages `zoom.js` offers. It is
|
|
4
4
|
* the whole zoom control; the live percentage is never drawn, it is what the
|
|
5
5
|
* trigger is named after ("Zoom, 62%"), so the bar carries one icon rather
|
|
6
6
|
* than a readout that changes width under the reader.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Radio semantics, not toggles: exactly one row is the current mode, and
|
|
21
21
|
* choosing the one already checked settles as a no-op in the element's own
|
|
22
22
|
* `#setMode`, so this module reports every row alike. The check follows
|
|
23
|
-
* the mode rather than the measurement — "Fit
|
|
23
|
+
* the mode rather than the measurement — "Fit page" is checked whenever the
|
|
24
24
|
* viewer is fitting, whatever percentage that came out at, and a host-set
|
|
25
25
|
* percentage the menu does not offer checks nothing.
|
|
26
26
|
*
|
|
@@ -310,7 +310,7 @@ export let zoomMenu = ({ mode, percent, choose }) => html`
|
|
|
310
310
|
@beforetoggle=${leaving}
|
|
311
311
|
@keydown=${navigate}
|
|
312
312
|
>
|
|
313
|
-
${row("Fit
|
|
313
|
+
${row("Fit page", "fit", mode === "fit", choose)}
|
|
314
314
|
<hr class="qv-separator" role="separator" />
|
|
315
315
|
${STEPS.map((step) => row(step + "%", step, mode === step, choose))}
|
|
316
316
|
</div>
|