@panelwave/player 1.0.0 → 1.1.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/README.md +243 -24
- package/fesm2022/panelwave-player.mjs +501 -175
- package/fesm2022/panelwave-player.mjs.map +1 -1
- package/index.d.ts +181 -22
- package/package.json +1 -1
- package/src/assets/fonts/balloon/LICENSES.md +33 -33
- package/src/assets/fonts/balloon/balloon-fonts.css +227 -227
- package/src/assets/i18n/de.json +26 -1
- package/src/assets/i18n/en.json +26 -1
package/README.md
CHANGED
|
@@ -1,24 +1,243 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
# @panelwave/player
|
|
2
|
+
|
|
3
|
+
**The open-source Angular player for interactive graphic novels.** It renders
|
|
4
|
+
[PanelWave](https://panelwave.org) manifests (`panelwave.json`): branching panel
|
|
5
|
+
graphs, layered artwork, comic speech balloons, hotspots, video panels, audio,
|
|
6
|
+
story variables, multilingual text and paywalls, all in one component.
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/@panelwave/player)
|
|
9
|
+
[](https://github.com/panelwave/player/blob/master/LICENSE)
|
|
10
|
+
[](https://angular.dev)
|
|
11
|
+
|
|
12
|
+
**[Live demo](https://panelwave.github.io/player/)** ·
|
|
13
|
+
**[Documentation](https://docs.panelwave.org/player/overview)** ·
|
|
14
|
+
**[Format spec](https://github.com/panelwave/schema)** ·
|
|
15
|
+
**[Source](https://github.com/panelwave/player)**
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
A PanelWave story is a **directed graph of panels**. Each chapter has an entry
|
|
22
|
+
panel and edges between panels. Edges can carry a transition and a
|
|
23
|
+
[JSON Logic](https://jsonlogic.com) condition, so readers' choices and story
|
|
24
|
+
variables decide where the story goes. The player takes that manifest and turns
|
|
25
|
+
it into a complete reading experience:
|
|
26
|
+
|
|
27
|
+
- **Graph navigation:** branching and conditional paths, back/forward history,
|
|
28
|
+
resume from the last position, per-edge transitions.
|
|
29
|
+
- **Two view modes:** classic one-panel-at-a-time reading, or an
|
|
30
|
+
**infinite canvas** where panels sit on a 2D stage and the camera pans and
|
|
31
|
+
zooms between them.
|
|
32
|
+
- **Layered panels:** image, video, text, audio and plugin layers, with
|
|
33
|
+
responsive image variants (`w640`–`w2560`) and smart preloading.
|
|
34
|
+
- **Speech balloons:** an SVG lettering engine with 9 balloon types (normal,
|
|
35
|
+
thought, shout, whisper, connector, …). Tails, style presets and
|
|
36
|
+
per-character overrides are all configurable.
|
|
37
|
+
- **Hotspots:** clickable areas that navigate, set variables, open extras or
|
|
38
|
+
modals, or talk to plugins. They're keyboard-accessible and tracked.
|
|
39
|
+
- **Variables and variants:** five scopes (global, chapter, page, session,
|
|
40
|
+
persistent). Panel variants swap content based on variables, e.g. an
|
|
41
|
+
age-appropriate version.
|
|
42
|
+
- **Audio and video:** a WebAudio mixer (ambient, music, voice-over, SFX) and
|
|
43
|
+
sequenced video panels.
|
|
44
|
+
- **Localization:** switch languages at runtime with fallback chains,
|
|
45
|
+
per-locale assets and RTL support. The UI ships in English and German.
|
|
46
|
+
- **Monetization hooks:** paywall rules from the manifest (subscription,
|
|
47
|
+
purchase, age gates), evaluated against what the reader owns. Your app
|
|
48
|
+
handles checkout; the player never talks to a payment provider itself.
|
|
49
|
+
- **Accessibility:** full keyboard control, screen-reader announcements,
|
|
50
|
+
`prefers-reduced-motion`, content warnings, and no serious axe violations.
|
|
51
|
+
- **Reader toolbar:** table of contents, thumbnails, autoplay, settings,
|
|
52
|
+
share, like and bookmark.
|
|
53
|
+
|
|
54
|
+
Stories are authored in the PanelWave CMS or written by hand against the
|
|
55
|
+
[open JSON Schema](https://github.com/panelwave/schema). The player supports
|
|
56
|
+
manifests up to format 1.6 (video panels, edge-transition inheritance,
|
|
57
|
+
typography style presets, the infinite canvas, paywall rules unlocked by any
|
|
58
|
+
of several products, each Buy option named and priced from `paywall.products`; 1.5's asset folders and localization blocks are
|
|
59
|
+
authoring metadata the player doesn't need).
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install @panelwave/player @ngx-translate/core
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Peer dependencies: `@angular/core` and `@angular/common` **^20**,
|
|
68
|
+
`@ngx-translate/core` **^17**, `rxjs` **^7.8**.
|
|
69
|
+
|
|
70
|
+
## Setup
|
|
71
|
+
|
|
72
|
+
**1. Providers.** The player loads manifests over HTTP and uses ngx-translate
|
|
73
|
+
for its UI strings:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// app.config.ts
|
|
77
|
+
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
|
|
78
|
+
import { HttpClient, provideHttpClient } from '@angular/common/http';
|
|
79
|
+
import { TranslateLoader, TranslateModule, TranslationObject } from '@ngx-translate/core';
|
|
80
|
+
import { Observable } from 'rxjs';
|
|
81
|
+
|
|
82
|
+
class PlayerTranslateLoader implements TranslateLoader {
|
|
83
|
+
constructor(private http: HttpClient) {}
|
|
84
|
+
getTranslation(lang: string): Observable<TranslationObject> {
|
|
85
|
+
return this.http.get<TranslationObject>(`./assets/i18n/${lang}.json`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const appConfig: ApplicationConfig = {
|
|
90
|
+
providers: [
|
|
91
|
+
provideHttpClient(),
|
|
92
|
+
importProvidersFrom(
|
|
93
|
+
TranslateModule.forRoot({
|
|
94
|
+
loader: { provide: TranslateLoader, useClass: PlayerTranslateLoader, deps: [HttpClient] },
|
|
95
|
+
}),
|
|
96
|
+
),
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**2. Assets and fonts.** Copy the player's UI translations into your app,
|
|
102
|
+
and include the balloon lettering fonts:
|
|
103
|
+
|
|
104
|
+
```jsonc
|
|
105
|
+
// angular.json → projects.<app>.architect.build.options
|
|
106
|
+
"assets": [
|
|
107
|
+
{ "glob": "*.json", "input": "node_modules/@panelwave/player/src/assets/i18n", "output": "assets/i18n" }
|
|
108
|
+
],
|
|
109
|
+
"styles": [
|
|
110
|
+
"node_modules/@panelwave/player/src/assets/fonts/balloon/balloon-fonts.css",
|
|
111
|
+
"src/styles.css"
|
|
112
|
+
]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The bundled fonts are open-licensed (SIL OFL 1.1: Bangers, Comic Neue,
|
|
116
|
+
Caveat, Anton and 10 more). The commercial Blambot font **Ames Pro** is not
|
|
117
|
+
included. If your works use it, license it from [blambot.com](https://blambot.com)
|
|
118
|
+
and add your own `@font-face`. Otherwise balloons fall back to Comic Neue.
|
|
119
|
+
|
|
120
|
+
## Usage
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { Component } from '@angular/core';
|
|
124
|
+
import { PlayerShellComponent } from '@panelwave/player';
|
|
125
|
+
|
|
126
|
+
@Component({
|
|
127
|
+
selector: 'app-reader',
|
|
128
|
+
imports: [PlayerShellComponent],
|
|
129
|
+
template: `
|
|
130
|
+
<pw-player-shell
|
|
131
|
+
manifestUrl="/stories/my-story/panelwave.json"
|
|
132
|
+
locale="en-US"
|
|
133
|
+
[showToolbar]="true"
|
|
134
|
+
(panelChange)="onPanel($event.panel.id)"
|
|
135
|
+
(error)="onError($event)" />
|
|
136
|
+
`,
|
|
137
|
+
styles: `:host { display: block; height: 100dvh; }`,
|
|
138
|
+
})
|
|
139
|
+
export class ReaderComponent {
|
|
140
|
+
onPanel(id: string) { console.log('now showing', id); }
|
|
141
|
+
onError(err: Error) { console.error(err); }
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
You can also pass a manifest object instead of a URL: `[manifest]="manifest"`
|
|
146
|
+
(type `PanelWaveManifest`).
|
|
147
|
+
|
|
148
|
+
### Inputs
|
|
149
|
+
|
|
150
|
+
| Input | Type | Default | Description |
|
|
151
|
+
|---|---|---|---|
|
|
152
|
+
| `manifestUrl` | `string` | | Load the manifest from a URL |
|
|
153
|
+
| `manifest` | `PanelWaveManifest` | | Or pass the manifest object directly |
|
|
154
|
+
| `locale` | `LocaleCode` | `'en-US'` | Initial locale (BCP-47) |
|
|
155
|
+
| `initialChapterId` / `initialPanelId` | `string` | | Start at a specific position (otherwise: bookmark, then chapter entry) |
|
|
156
|
+
| `initialVariables` | `Record<string, unknown>` | | Seed story variables once at start. May set `readOnly` variables, e.g. a verified `user.age` from your account system |
|
|
157
|
+
| `showToolbar` | `boolean` | `false` | Show the reader toolbar |
|
|
158
|
+
| `autoplay` | `boolean` | `false` | Start auto-advancing; the reader's toolbar toggle takes over afterwards |
|
|
159
|
+
| `secondsPerPanel` | `number` | `5` | Autoplay interval |
|
|
160
|
+
| `reducedMotion` | `boolean` | `false` | Force reduced motion (the OS setting and the reader's preference also apply) |
|
|
161
|
+
| `viewModeOverride` | `'auto' \| 'panel' \| 'canvas'` | `'auto'` | Force panel or infinite-canvas view |
|
|
162
|
+
| `entitlementSnapshot` | `EntitlementSnapshot` | anonymous | What the reader owns; turns the manifest's `paywall.rules` on |
|
|
163
|
+
| `entitlementEndpoint` / `readerToken` | `string` | | Let the player fetch the snapshot itself (`{workId}` is substituted) |
|
|
164
|
+
|
|
165
|
+
Inputs may change after init: a new `manifest` / `manifestUrl` reloads the
|
|
166
|
+
work (position, non-persistent variables, open overlays and autoplay reset),
|
|
167
|
+
a new `entitlementSnapshot` re-evaluates the gates, and `locale`,
|
|
168
|
+
`viewModeOverride`, `showToolbar`, `reducedMotion`, `secondsPerPanel` and
|
|
169
|
+
`autoplay` apply live.
|
|
170
|
+
|
|
171
|
+
### Outputs
|
|
172
|
+
|
|
173
|
+
| Output | Payload | Fires when |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| `ready` | `void` | The manifest is loaded and the player is ready |
|
|
176
|
+
| `panelChange` | `{ panel, chapter }` | The reader moves to a new panel |
|
|
177
|
+
| `chapterChange` | `Chapter` | A chapter boundary is crossed |
|
|
178
|
+
| `variableChange` | `{ key, value }` | A story variable changes |
|
|
179
|
+
| `localeChange` | `LocaleCode` | The language is switched |
|
|
180
|
+
| `navigationAttempt` | `{ direction, target? }` | Any navigation attempt, allowed or not |
|
|
181
|
+
| `cameraChange` | `CameraState` | The canvas-view camera moves |
|
|
182
|
+
| `paywallAction` | `{ action, gate, productId? }` | The reader clicks Buy (`purchase`), Subscribe (`subscribe`), Sign in (`login`) or Maybe later (`dismiss`) on a paywall; `productId` is the chosen product id or tier |
|
|
183
|
+
| `ageVerified` | `AgeVerificationResult` | The reader answers an age gate |
|
|
184
|
+
| `likeChange` / `bookmarkChange` | `{ workId, … }` | Like or bookmark toggled (also stored locally) |
|
|
185
|
+
| `error` | `Error` | A loading or runtime error happens |
|
|
186
|
+
|
|
187
|
+
### Paywalls
|
|
188
|
+
|
|
189
|
+
Declare rules in the manifest's `paywall` block. Then tell the player what the
|
|
190
|
+
reader owns, and handle checkout yourself:
|
|
191
|
+
|
|
192
|
+
```html
|
|
193
|
+
<pw-player-shell
|
|
194
|
+
[manifestUrl]="url"
|
|
195
|
+
[entitlementSnapshot]="{ subscriptionTier: null, purchasedProductIds: ['chapter-2'], ageVerified: false }"
|
|
196
|
+
(paywallAction)="onPaywall($event)" />
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Readers without access see the free preview and a paywall overlay, and
|
|
200
|
+
navigation stops at the gate. Works without paywall rules aren't affected.
|
|
201
|
+
|
|
202
|
+
- **Scopes:** `work` rules gate everything after their free preview;
|
|
203
|
+
`chapter` rules gate only the chapter named by `refId`, with the preview
|
|
204
|
+
counted within that chapter; `panel` rules gate exactly their panels;
|
|
205
|
+
`extras` rules lock an extras block, never panels.
|
|
206
|
+
- **Age:** `ageGate` / `minimumAge` is checked on top of the rule's
|
|
207
|
+
entitlement. The reader answers the age gate first, then gets the paywall
|
|
208
|
+
if the purchase or subscription is still missing.
|
|
209
|
+
- **Buy / Subscribe:** the overlay offers a Buy option per product of a
|
|
210
|
+
purchase rule and a Subscribe option per tier of a subscription rule (name,
|
|
211
|
+
description and price from the rule), next to Sign in and Maybe later.
|
|
212
|
+
`paywallAction` emits `{ action: 'purchase' | 'subscribe' | 'login' |
|
|
213
|
+
'dismiss', gate, productId? }`; the options are also on `gate.options`.
|
|
214
|
+
- **After checkout:** call `refreshEntitlements(snapshot)` on the shell or
|
|
215
|
+
pass a new `entitlementSnapshot`; the overlay closes once the reader is
|
|
216
|
+
through. Story keys and swipes do nothing while the paywall or the age
|
|
217
|
+
gate is open.
|
|
218
|
+
|
|
219
|
+
### Going further
|
|
220
|
+
|
|
221
|
+
The package also exports the services and building blocks behind the shell:
|
|
222
|
+
`PlayerStateService` (observable reading state), `VariableStoreService`,
|
|
223
|
+
`FlowEngineService`, `TranslationService`, `PluginHostService` (message bus
|
|
224
|
+
for sandboxed plugin layers), the `ComicBalloon` renderer with
|
|
225
|
+
`mergeBalloonConfig`, and all manifest types. See the
|
|
226
|
+
[documentation](https://docs.panelwave.org/player/overview) for the full API.
|
|
227
|
+
|
|
228
|
+
## Browser support
|
|
229
|
+
|
|
230
|
+
Chrome / Edge 120+, Firefox 120+, Safari 17+ (desktop and iOS),
|
|
231
|
+
Android Chrome 120+.
|
|
232
|
+
|
|
233
|
+
## Related packages
|
|
234
|
+
|
|
235
|
+
- [`@panelwave/types`](https://www.npmjs.com/package/@panelwave/types): TypeScript types for PanelWave manifests
|
|
236
|
+
- [`@panelwave/cli`](https://www.npmjs.com/package/@panelwave/cli): validate, bundle, diff and upgrade manifests
|
|
237
|
+
- [PanelWave format](https://github.com/panelwave/schema): the open JSON Schema (CC BY 4.0)
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
[MIT](https://github.com/panelwave/player/blob/master/LICENSE). The bundled
|
|
242
|
+
balloon fonts are under SIL OFL 1.1 (see `src/assets/fonts/balloon/LICENSES.md`),
|
|
243
|
+
and the UI icons are from [Lucide](https://lucide.dev) (ISC).
|