@fabi-play/sdk 0.3.0 → 0.5.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 +84 -0
- package/dist/avatars.d.ts +26 -14
- package/dist/avatars.js +1 -1
- package/dist/chunks/{chunk-GFNOIOSX.js → chunk-H3BKYVPL.js} +301 -22
- package/dist/fabi-play.d.ts +193 -10
- package/dist/fabi-play.js +1 -1
- package/dist/fabi-play.schema.json +32 -3
- package/dist/fabi-play.umd.js +453 -26
- package/dist/fabi-play.umd.min.js +1 -1
- package/dist/index.d.ts +212 -11
- package/dist/index.js +166 -4
- package/dist/integrity.json +6 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -45,6 +45,38 @@ From the CDN, pinned to an exact version with its Subresource Integrity hash (li
|
|
|
45
45
|
| `fabi-play.d.ts` | types for script-tag games (`FabiPlay.PlayerFrame`, …) |
|
|
46
46
|
| `fabi-play.schema.json` | the JSON Schema of `fabi-play.json` |
|
|
47
47
|
|
|
48
|
+
## The cover
|
|
49
|
+
|
|
50
|
+
The game's card in the catalogue (the TV's carousel, the phone, the admin) is its cover, an image you draw. Put it
|
|
51
|
+
with the game's files and name it in `fabi-play.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
"cover": "cover.jpg"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- **16:9**, 1280×720 recommended, at most 150 KB (`COVER_MAX_BYTES`). `.jpg`, `.jpeg`, `.png` or `.webp`; JPG
|
|
58
|
+
(quality about 80) is the safest on old TV browsers.
|
|
59
|
+
- **A path relative to the game's address**, like `entry`: no scheme, no leading `/`, no `\`, no `.` or `..`
|
|
60
|
+
segments, extension in lowercase, up to 128 characters.
|
|
61
|
+
- **Optional.** Fabi Play's review checks the file (it exists, it is an image, its size, its shape: 16:9 within 2%,
|
|
62
|
+
`COVER_ASPECT`, `COVER_ASPECT_TOLERANCE`) and warns about a problem; without a working cover the catalogue shows
|
|
63
|
+
another picture of the game.
|
|
64
|
+
|
|
65
|
+
## Portrait (a phone held upright)
|
|
66
|
+
|
|
67
|
+
A phone alone («Gioca qui / Play here») is held upright, close to the child (about one step away, upper body in
|
|
68
|
+
view), and offers only games that say they can be played in portrait:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
"orientations": ["landscape", "portrait"]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- **Optional**, `["landscape"]` by default; non-empty, no repeats, from `"landscape"` and `"portrait"`.
|
|
75
|
+
- `play.orientation` (and the `start` event's `orientation`) says the one the game started in: `'portrait'` or
|
|
76
|
+
`'landscape'` (also what older hosts mean). Use it to choose a layout, but size everything from the real viewport
|
|
77
|
+
(`innerWidth` × `innerHeight`).
|
|
78
|
+
- On a phone the child is close and often seen from the head to the knees: don't rely on the feet being visible.
|
|
79
|
+
|
|
48
80
|
## Fabi explains your game (intro)
|
|
49
81
|
|
|
50
82
|
Before calibration, Fabi (the mascot) can explain your game on the TV in a few steps: a speech bubble over an image
|
|
@@ -127,6 +159,25 @@ opts in with `inputs` in `fabi-play.json`:
|
|
|
127
159
|
`"remote"` must work with the arrow keys alone and, where it has things to pick (bubbles, answers, buttons), let a
|
|
128
160
|
pointer click them as well: pointer events, large hit areas, a hover highlight. Fabi Play's own menus, pickers
|
|
129
161
|
and turn screen are clickable too.
|
|
162
|
+
- **The arrows as a free cursor (`"remoteCursor": true`).** A game picked or drawn with a pointer (a painting
|
|
163
|
+
game) can ask for the arrows to move a cursor instead of making gestures, with `"remoteCursor": true` in
|
|
164
|
+
`fabi-play.json` (only next to `"remote"` in `inputs`):
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
"inputs": ["body", "remote"], "remoteCursor": true
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Then, in remote mode, the screen draws a cursor above the game and moves it with the arrows: slowly at first, for
|
|
171
|
+
precision, faster the longer an arrow is held, stopping at the edges. OK presses it: held, it stays pressed until
|
|
172
|
+
released (OK held = paint); a quick OK is a click. Back is still Fabi Play's pause. The game gets it exactly like
|
|
173
|
+
a Magic Remote: `play.pointer` / `FabiPlay.hasPointer` is `true`, and the SDK makes ordinary pointer and mouse
|
|
174
|
+
events in the page at the cursor (`pointermove`/`mousemove`, `pointerover`/`pointerout`, `pointerdown`/`mousedown`
|
|
175
|
+
while OK goes down, `pointerup`/`mouseup` then `click` when it comes up; `pointerType` `'mouse'`; at a pause, a
|
|
176
|
+
break or a turn a held OK is let go with `pointercancel` and `mouseup`, no click). So a game written for the
|
|
177
|
+
Magic Remote's pointer works with the arrows unchanged, and the other way round. The arrows make no gestures
|
|
178
|
+
then: no lanes, no jump, no squat. `play.on('cursor', ({ x, y, down }) => …)` gives the raw position (fractions of
|
|
179
|
+
the page) if you want it, but the events are enough. Old TV browsers without `PointerEvent` get the mouse events
|
|
180
|
+
only: listen to both, or to mouse events.
|
|
130
181
|
- **One remote is one child; several children take turns, a round each, if the game follows the contract.**
|
|
131
182
|
Turns are not free: the game must follow the steps below, and say so in `fabi-play.json` with
|
|
132
183
|
`"turns": true` (only next to `"remote"` in `inputs`): `"inputs": ["body", "remote"], "turns": true`. A game
|
|
@@ -159,6 +210,39 @@ opts in with `inputs` in `fabi-play.json`:
|
|
|
159
210
|
- **Games aimed with the hands** (`movements: ["hands"]`, the `pointer()` adapter) should not list `"remote"`
|
|
160
211
|
unless they offer a remote-friendly selection: a child with a remote has no hand cursor.
|
|
161
212
|
|
|
213
|
+
## Saving a picture on the phone
|
|
214
|
+
|
|
215
|
+
A game can save a picture on the family's phone, in its gallery: the drawing at the end, the photo of a wall before
|
|
216
|
+
it is cleared.
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
if (play.canSavePicture) {
|
|
220
|
+
const result = await play.savePicture(canvas, { title: 'Il nostro muro' })
|
|
221
|
+
// 'sent' | 'unavailable' | 'too-big' | 'error'
|
|
222
|
+
}
|
|
223
|
+
play.on('capabilities', ({ pictures }) => showSaveButton(pictures))
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
With the script tag: `FabiPlay.savePicture(canvas, { title })`, `FabiPlay.canSavePicture`,
|
|
227
|
+
`FabiPlay.on('capabilities', ({ pictures }) => …)`, and `canSavePicture` in the `start` event.
|
|
228
|
+
|
|
229
|
+
- **`canSavePicture`** is `true` when the screen says a phone that can save pictures is linked (the Fabi Play app;
|
|
230
|
+
the web phone can't, and nor can TV-only remote sessions). It can change mid-game (a phone joins, the last one
|
|
231
|
+
leaves): the `capabilities` event says so. `false` before the start and from older screens.
|
|
232
|
+
- **`savePicture(source, { title })`** takes a canvas or a data URL. A canvas is scaled down to 1920 px on its
|
|
233
|
+
longer side, drawn on white (JPEG has no transparency) and encoded as JPEG at quality 0.85 (lower, then smaller,
|
|
234
|
+
if it would be over 3 MiB). A data URL must be `data:image/jpeg;base64,…` or `data:image/png;base64,…`, at most
|
|
235
|
+
3 MiB, and is sent as it is. `title` (optional, up to 60 characters) names the file. A canvas tainted by another
|
|
236
|
+
site's image can't be read: draw only your own images on it.
|
|
237
|
+
- **It resolves, never rejects**, with `'sent'` (the phone has it), `'unavailable'` (`canSavePicture` is false),
|
|
238
|
+
`'too-big'` (over 3 MiB) or `'error'` (not an image, a canvas that can't be read, the link dropped, no answer in
|
|
239
|
+
2 minutes). Never make the children wait on it: show the picture "flying into the phone" and go on.
|
|
240
|
+
- **If the phone can't save it** (the parent denied the permission), Fabi Play tells the parent on the phone; the
|
|
241
|
+
game is told `'sent'` or `'error'`, and should not tell the children.
|
|
242
|
+
- **Privacy.** The picture goes only from the TV to the phone, over the direct link they already share (WebRTC,
|
|
243
|
+
encrypted), never through or to a server, and no copy is kept anywhere else. Only a JPEG or PNG image can travel
|
|
244
|
+
this way (the screen checks the data URL and its bytes): a game can send nothing else to the phone.
|
|
245
|
+
|
|
162
246
|
Docs: https://docs.fabiplay.app.
|
|
163
247
|
|
|
164
248
|
## License
|
package/dist/avatars.d.ts
CHANGED
|
@@ -106,21 +106,21 @@ export interface Palette {
|
|
|
106
106
|
hair?: string;
|
|
107
107
|
}
|
|
108
108
|
/** The head's outline. */
|
|
109
|
-
export type HeadShape = "round" | "wide" | "tall" | "square" | "bulb" | "bucket";
|
|
109
|
+
export type HeadShape = "round" | "wide" | "tall" | "square" | "bulb" | "bucket" | "snout-long";
|
|
110
110
|
/** The ears (or what is in their place: horns, antennae, a fin). */
|
|
111
|
-
export type EarKind = "none" | "pointy" | "round" | "big-round" | "long" | "side" | "tufts" | "antenna" | "antennae" | "horns" | "unicorn" | "floppy" | "droopy" | "fin" | "human";
|
|
111
|
+
export type EarKind = "none" | "pointy" | "round" | "big-round" | "long" | "side" | "tufts" | "antenna" | "antennae" | "horns" | "unicorn" | "floppy" | "droopy" | "fin" | "human" | "pixie";
|
|
112
112
|
/** How the eyes are drawn. */
|
|
113
113
|
export type EyeKind = "dot" | "big" | "mask" | "screen" | "almond" | "visor" | "calm" | "slit" | "goggle" | "bead";
|
|
114
114
|
/** How the mouth (or beak) is drawn. */
|
|
115
|
-
export type MouthKind = "none" | "smile" | "open" | "cat" | "beak" | "teeth" | "grill" | "tiny" | "smirk" | "bill" | "hook" | "jaws";
|
|
115
|
+
export type MouthKind = "none" | "smile" | "open" | "cat" | "beak" | "teeth" | "grill" | "tiny" | "smirk" | "bill" | "hook" | "jaws" | "toothy";
|
|
116
116
|
/** The patch around the nose and mouth. */
|
|
117
117
|
export type MuzzleKind = "none" | "oval" | "heart" | "monkey" | "disc" | "cheeks" | "band";
|
|
118
118
|
/** The tail, behind the body. */
|
|
119
|
-
export type TailKind = "none" | "fluffy" | "brush" | "pom" | "long" | "curl" | "tuft" | "striped" | "spiky" | "arrow" | "fish" | "plume";
|
|
119
|
+
export type TailKind = "none" | "fluffy" | "brush" | "pom" | "long" | "curl" | "tuft" | "striped" | "spiky" | "arrow" | "fish" | "plume" | "thick";
|
|
120
120
|
/** Things on the head. */
|
|
121
|
-
export type HeadExtra = "mane" | "rainbow-mane" | "stripes" | "spikes" | "spots" | "curl" | "helmet" | "bolts" | "whiskers" | "tuft" | "brows" | "eggshell" | "wool" | "eye-spot" | "crest" | "forehead" | "gills" | "ruff" | "hair" | "curls" | "locks" | "ninja-hood" | "cap" | "safari-hat" | "wizard-hat" | "plume" | "stripe" | "bandana" | "eye-patch" | "dive-mask" | "domino";
|
|
121
|
+
export type HeadExtra = "mane" | "rainbow-mane" | "stripes" | "spikes" | "spots" | "curl" | "helmet" | "bolts" | "whiskers" | "tuft" | "brows" | "eggshell" | "wool" | "eye-spot" | "crest" | "forehead" | "gills" | "ruff" | "hair" | "curls" | "locks" | "ninja-hood" | "cap" | "safari-hat" | "wizard-hat" | "plume" | "stripe" | "bandana" | "eye-patch" | "dive-mask" | "domino" | "bun" | "flower-crown";
|
|
122
122
|
/** Things on the body. */
|
|
123
|
-
export type BodyExtra = "wings" | "backpack" | "sheet" | "tentacles" | "panel" | "badge" | "joints" | "feathers" | "flippers" | "boots" | "scarf" | "spots" | "wool" | "robe" | "cape" | "fins" | "tank" | "straps" | "belt" | "stripes" | "pauldrons" | "emblem" | "pocket" | "racing-stripe" | "star";
|
|
123
|
+
export type BodyExtra = "wings" | "backpack" | "sheet" | "tentacles" | "panel" | "badge" | "joints" | "feathers" | "flippers" | "boots" | "scarf" | "spots" | "wool" | "robe" | "cape" | "fins" | "tank" | "straps" | "belt" | "stripes" | "pauldrons" | "emblem" | "pocket" | "racing-stripe" | "star" | "tiny-arms" | "strong-legs" | "wings-fairy" | "wand" | "petals";
|
|
124
124
|
/** An avatar's face. */
|
|
125
125
|
export interface FaceDef {
|
|
126
126
|
/** How the eyes are drawn. */
|
|
@@ -142,7 +142,7 @@ export interface FaceDef {
|
|
|
142
142
|
/** Eyebrows of this colour (people): a little attitude. */
|
|
143
143
|
brows?: string;
|
|
144
144
|
}
|
|
145
|
-
/** The age band an avatar is for, or 'all':
|
|
145
|
+
/** The age band an avatar is for, or 'all': of no band (Fabi the fox, the app's mascot: the host, in no picker). */
|
|
146
146
|
export type AvatarBand = AgeRange | "all";
|
|
147
147
|
/** An avatar, as data: one renderer draws any definition. */
|
|
148
148
|
export interface AvatarDef {
|
|
@@ -154,11 +154,17 @@ export interface AvatarDef {
|
|
|
154
154
|
it: string;
|
|
155
155
|
};
|
|
156
156
|
/**
|
|
157
|
-
* The age band the avatar is for: its style and its body proportions (see BAND_BODY in rig.ts). 'all': in
|
|
158
|
-
* band's list, drawn with the 6–8 proportions.
|
|
157
|
+
* The age band the avatar is for, its home: its style and its body proportions (see BAND_BODY in rig.ts). 'all': in
|
|
158
|
+
* no band's list (the mascot), drawn with the 6–8 proportions. See alsoIn for an avatar offered in other bands too.
|
|
159
159
|
*/
|
|
160
160
|
band: AvatarBand;
|
|
161
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* Other age bands the avatar is also offered in (listed in their AVATAR_BANDS too), besides its own band. Shown in
|
|
163
|
+
* one of them, it is drawn with that band's proportions (bodyBand(def, band)); its band stays its home: its style
|
|
164
|
+
* (outline), bandOf, and its proportions when the band shown is not known. Not for 'all' (the mascot).
|
|
165
|
+
*/
|
|
166
|
+
alsoIn?: readonly AgeRange[];
|
|
167
|
+
/** A special avatar (the mascot): the host, not a player; in no band's list (AVATAR_BANDS). */
|
|
162
168
|
special?: boolean;
|
|
163
169
|
/** Its colours. */
|
|
164
170
|
palette: Palette;
|
|
@@ -223,9 +229,12 @@ export interface BandBody {
|
|
|
223
229
|
export declare const BAND_BODY: Record<AgeRange, BandBody>;
|
|
224
230
|
export interface RigOptions {
|
|
225
231
|
mirrored?: boolean;
|
|
226
|
-
/** Portraits leave the tail out: in a head-and-shoulders crop
|
|
232
|
+
/** Portraits leave the tail and the wand out: in a head-and-shoulders crop they would only peek in as stray shapes. */
|
|
227
233
|
portrait?: boolean;
|
|
228
|
-
/**
|
|
234
|
+
/**
|
|
235
|
+
* Body proportions (see BAND_BODY); 6–8 by default. For avatarShapes, the band the avatar is shown in: it follows
|
|
236
|
+
* it only for an avatar offered there too (alsoIn, see bodyBand); otherwise the avatar's own band.
|
|
237
|
+
*/
|
|
229
238
|
band?: AgeRange;
|
|
230
239
|
}
|
|
231
240
|
/** The torso frame: origin between the shoulders, r to the right, d down along the torso; lengths in pixels. */
|
|
@@ -250,7 +259,10 @@ export declare function avatarShapes(body: Body, def: AvatarDef, box: Box, opts?
|
|
|
250
259
|
export declare const DEFAULT_AVATAR: AvatarDef;
|
|
251
260
|
/** The avatar with this id, or undefined. */
|
|
252
261
|
export declare const avatarById: (id: string) => AvatarDef | undefined;
|
|
253
|
-
/**
|
|
254
|
-
|
|
262
|
+
/**
|
|
263
|
+
* The portrait: head and shoulders of the neutral pose, framed in a size × size square at (0, 0). band: the band the
|
|
264
|
+
* picker shows (see DrawOptions.band).
|
|
265
|
+
*/
|
|
266
|
+
export declare function iconShapes(def: AvatarDef | string, size: number, band?: AgeRange): Shape[];
|
|
255
267
|
|
|
256
268
|
export {};
|