@johnmorrisdotca/kyogijo-stadiums 0.50.0 → 0.52.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 +22 -0
- package/NOTICE.md +11 -0
- package/README.md +31 -2
- package/catalogue.json +1208 -0
- package/dist/catalogue-entry.d.ts +2 -0
- package/dist/catalogue-entry.js +3 -0
- package/dist/catalogue.d.ts +45 -0
- package/dist/catalogue.data.d.ts +2 -0
- package/dist/catalogue.data.js +1208 -0
- package/dist/catalogue.js +74 -0
- package/dist/catalogue.types.d.ts +143 -0
- package/dist/catalogue.types.js +1 -0
- package/dist/element.d.ts +1 -0
- package/dist/element.js +16 -5
- package/dist/judging.mount.js +1 -0
- package/dist/replay.d.ts +4 -0
- package/dist/replay.dom.d.ts +67 -2
- package/dist/replay.dom.js +187 -114
- package/dist/replay.js +10 -1
- package/dist/strings.core.d.ts +3 -0
- package/dist/strings.en.js +2 -0
- package/dist/strings.ja.js +2 -0
- package/dist/turn-entry.d.ts +4 -0
- package/dist/turn-entry.js +2 -0
- package/dist/turn.dom.d.ts +22 -1
- package/dist/turn.dom.js +121 -8
- package/dist/turn.kit.d.ts +1 -1
- package/dist/turn.replay.d.ts +56 -0
- package/dist/turn.replay.dom.d.ts +23 -0
- package/dist/turn.replay.dom.js +139 -0
- package/dist/turn.replay.js +82 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +12 -2
package/dist/replay.dom.js
CHANGED
|
@@ -33,60 +33,30 @@ const ICONS = {
|
|
|
33
33
|
const icon = (d) => `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
|
|
34
34
|
const pathOf = (points) => points.map(([x, y], i) => `${i === 0 ? "M" : "L"}${num(x)} ${num(y)}`).join("");
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* begins) redraws the replay's own layers, never the surface. With `prefers-reduced-motion` nothing plays by itself and play steps instead.
|
|
36
|
+
* The painter of a replay: it moves the marks that move and nothing else. A mark that comes or goes (an object that enters, an event that begins) draws the replay's own group
|
|
37
|
+
* again, never the surface; a group whose marks cannot be matched to elements one for one is drawn again each time.
|
|
39
38
|
*/
|
|
40
|
-
export function
|
|
41
|
-
const doc =
|
|
42
|
-
|
|
43
|
-
const media = typeof matchMedia === "function" ? matchMedia("(prefers-reduced-motion: reduce)") : undefined;
|
|
44
|
-
const motionNow = () => (settings.motion === "smooth" ? "smooth" : settings.motion === "step" ? "step" : media?.matches === true ? "step" : "smooth");
|
|
45
|
-
const controller = createReplay(timeline, { ...settings, motion: motionNow() });
|
|
46
|
-
const id = initial.id ?? `ky-r${++counter}`;
|
|
47
|
-
let options = { ...initial, id };
|
|
48
|
-
let language = options.language === "ja" ? "ja" : "en";
|
|
49
|
-
const words = () => KYOGIJO_STRINGS[language].replay;
|
|
50
|
-
const root = doc.createElement("div");
|
|
51
|
-
root.className = "ky-replay";
|
|
52
|
-
const style = doc.createElement("style");
|
|
53
|
-
style.textContent = REPLAY_STYLE;
|
|
54
|
-
const stage = doc.createElement("div");
|
|
55
|
-
stage.className = "ky-stage";
|
|
56
|
-
stage.tabIndex = 0;
|
|
57
|
-
stage.setAttribute("role", "group");
|
|
58
|
-
const hint = doc.createElement("p");
|
|
59
|
-
hint.className = "ky-hint";
|
|
60
|
-
hint.id = `${id}-keys`;
|
|
61
|
-
const bar = doc.createElement("div");
|
|
62
|
-
bar.className = "ky-controls";
|
|
63
|
-
bar.setAttribute("role", "group");
|
|
64
|
-
const live = doc.createElement("div");
|
|
65
|
-
live.className = "ky-live";
|
|
66
|
-
live.setAttribute("role", "status");
|
|
67
|
-
live.setAttribute("aria-live", "polite");
|
|
68
|
-
root.append(style, stage, hint, bar, live);
|
|
69
|
-
host.replaceChildren(root);
|
|
70
|
-
// ---- The painter. ----
|
|
71
|
-
let svg = null;
|
|
72
|
-
let placer = replayPlacer(options, timeline);
|
|
39
|
+
export function replayPainter(source) {
|
|
40
|
+
const { doc, timeline, look } = source;
|
|
41
|
+
let placer = replayPlacer(source.options(), timeline);
|
|
73
42
|
/** For each group of the replay in the drawing, the marks it holds (their keys): a group whose keys changed is drawn again, the others only move. */
|
|
74
43
|
let shown = new Map();
|
|
75
44
|
let bound = new Map();
|
|
76
45
|
/** The groups whose marks cannot be matched to elements one for one, so that they are drawn again each time. */
|
|
77
46
|
const loose = new Set();
|
|
78
47
|
let painted = Number.NaN;
|
|
79
|
-
const
|
|
48
|
+
const keep = source.keep ?? (() => true);
|
|
49
|
+
const idOf = () => source.options().id ?? "";
|
|
80
50
|
const groupsAt = (t) => {
|
|
81
51
|
const snap = replaySnapshot(timeline, t, look);
|
|
82
|
-
const groups = replayGroups(timeline, snap, look, language);
|
|
52
|
+
const groups = replayGroups(timeline, snap, look, source.language()).filter(keep);
|
|
83
53
|
const places = placer(snap.objects.map((o) => [o.x, o.y]));
|
|
84
54
|
return { groups, placed: new Map(snap.objects.map((o, i) => [o.id, places[i]])) };
|
|
85
55
|
};
|
|
86
56
|
const positions = (g, placed) => (g.kind === "lines" ? g.coords.map((path) => placer(path)) : g.keys.map((k) => placed.get(k) ?? [0, 0]));
|
|
87
|
-
const holderOf = (g) => svg?.querySelector(`[id="${
|
|
57
|
+
const holderOf = (g) => source.svg()?.querySelector(`[id="${idOf()}-${typeof g === "string" ? g : g.id}"]`) ?? null;
|
|
88
58
|
/** Match the elements of a group that was just drawn to its marks, remembering where they are. */
|
|
89
|
-
const
|
|
59
|
+
const bindGroup = (g, placed) => {
|
|
90
60
|
bound.delete(g.id);
|
|
91
61
|
loose.delete(g.id);
|
|
92
62
|
if (!g.moving)
|
|
@@ -104,8 +74,9 @@ export function createReplayView(host, timeline, settings, initial) {
|
|
|
104
74
|
};
|
|
105
75
|
/** Draw the groups that changed again, in their place among the others, and take away the ones that are gone. The surface and the groups that only move are left. */
|
|
106
76
|
const patch = (groups, changed, gone, placed) => {
|
|
77
|
+
const svg = source.svg();
|
|
107
78
|
const container = svg?.querySelector("g.ky-layers") ?? null;
|
|
108
|
-
if (svg === null || container === null)
|
|
79
|
+
if (svg === null || svg === undefined || container === null)
|
|
109
80
|
return false;
|
|
110
81
|
for (const gid of gone) {
|
|
111
82
|
holderOf(gid)?.remove();
|
|
@@ -115,7 +86,7 @@ export function createReplayView(host, timeline, settings, initial) {
|
|
|
115
86
|
}
|
|
116
87
|
for (const g of changed) {
|
|
117
88
|
const scratch = doc.createElementNS(SVG_NS, "g");
|
|
118
|
-
scratch.innerHTML = renderLayers(options, [g.layer]).svg;
|
|
89
|
+
scratch.innerHTML = renderLayers(source.options(), [g.layer]).svg;
|
|
119
90
|
const made = scratch.querySelector("g.ky-layers > g.ky-layer");
|
|
120
91
|
if (made === null)
|
|
121
92
|
return false;
|
|
@@ -133,7 +104,7 @@ export function createReplayView(host, timeline, settings, initial) {
|
|
|
133
104
|
before.before(made);
|
|
134
105
|
}
|
|
135
106
|
shown.set(g.id, g.keys.join(","));
|
|
136
|
-
|
|
107
|
+
bindGroup(g, placed);
|
|
137
108
|
}
|
|
138
109
|
container.setAttribute("data-layers", String(container.querySelectorAll(":scope > g.ky-layer").length));
|
|
139
110
|
return true;
|
|
@@ -164,34 +135,59 @@ export function createReplayView(host, timeline, settings, initial) {
|
|
|
164
135
|
}
|
|
165
136
|
}
|
|
166
137
|
};
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
return;
|
|
170
|
-
painted = t;
|
|
171
|
-
const { groups, placed } = groupsAt(t);
|
|
172
|
-
const changed = groups.filter((g) => loose.has(g.id) || shown.get(g.id) !== g.keys.join(","));
|
|
173
|
-
const gone = [...shown.keys()].filter((gid) => !groups.some((g) => g.id === gid));
|
|
174
|
-
if (changed.length > 0 || gone.length > 0) {
|
|
175
|
-
if (!patch(groups, changed, gone, placed)) {
|
|
176
|
-
drawStage(t);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
move(groups, placed);
|
|
181
|
-
};
|
|
182
|
-
function drawStage(t) {
|
|
183
|
-
stage.innerHTML = drawFrame(options, timeline, t, look);
|
|
184
|
-
svg = stage.querySelector("svg.kyogijo");
|
|
185
|
-
placer = replayPlacer(options, timeline);
|
|
138
|
+
const bind = (t) => {
|
|
139
|
+
placer = replayPlacer(source.options(), timeline);
|
|
186
140
|
const { groups, placed } = groupsAt(t);
|
|
187
141
|
shown = new Map(groups.map((g) => [g.id, g.keys.join(",")]));
|
|
188
142
|
bound = new Map();
|
|
189
143
|
loose.clear();
|
|
190
144
|
painted = t;
|
|
191
145
|
for (const g of groups)
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
146
|
+
bindGroup(g, placed);
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
bind,
|
|
150
|
+
paint(t) {
|
|
151
|
+
if (source.svg() === null || t === painted)
|
|
152
|
+
return;
|
|
153
|
+
painted = t;
|
|
154
|
+
const { groups, placed } = groupsAt(t);
|
|
155
|
+
const changed = groups.filter((g) => loose.has(g.id) || shown.get(g.id) !== g.keys.join(","));
|
|
156
|
+
const gone = [...shown.keys()].filter((gid) => !groups.some((g) => g.id === gid));
|
|
157
|
+
if (changed.length > 0 || gone.length > 0) {
|
|
158
|
+
if (!patch(groups, changed, gone, placed)) {
|
|
159
|
+
source.redraw(t);
|
|
160
|
+
bind(t);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
move(groups, placed);
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/** Make the clock, the controls and the keys of a replay. Nothing runs until the reader (or `autoplay`) plays it, and nothing runs while it is paused, hidden or out of sight. */
|
|
169
|
+
export function replayDriver(doc, timeline, settings, source) {
|
|
170
|
+
const info = replayInfo(timeline);
|
|
171
|
+
const media = typeof matchMedia === "function" ? matchMedia("(prefers-reduced-motion: reduce)") : undefined;
|
|
172
|
+
const motionNow = () => (settings.motion === "smooth" ? "smooth" : settings.motion === "step" ? "step" : media?.matches === true ? "step" : "smooth");
|
|
173
|
+
const controller = createReplay(timeline, { ...settings, motion: motionNow() });
|
|
174
|
+
const id = `ky-rd${++counter}`;
|
|
175
|
+
let language = source.language;
|
|
176
|
+
const words = () => KYOGIJO_STRINGS[language].replay;
|
|
177
|
+
const style = doc.createElement("style");
|
|
178
|
+
style.textContent = REPLAY_STYLE;
|
|
179
|
+
const hint = doc.createElement("p");
|
|
180
|
+
hint.className = "ky-hint";
|
|
181
|
+
hint.id = `${id}-keys`;
|
|
182
|
+
const bar = doc.createElement("div");
|
|
183
|
+
bar.className = "ky-controls";
|
|
184
|
+
bar.setAttribute("role", "group");
|
|
185
|
+
const live = doc.createElement("div");
|
|
186
|
+
live.className = "ky-live";
|
|
187
|
+
live.setAttribute("role", "status");
|
|
188
|
+
live.setAttribute("aria-live", "polite");
|
|
189
|
+
if (settings.controls === false)
|
|
190
|
+
bar.hidden = true;
|
|
195
191
|
let sync = () => { };
|
|
196
192
|
const buildBar = () => {
|
|
197
193
|
const w = words();
|
|
@@ -241,17 +237,19 @@ export function createReplayView(host, timeline, settings, initial) {
|
|
|
241
237
|
speed.hidden = stepMode;
|
|
242
238
|
loop.hidden = stepMode;
|
|
243
239
|
bar.append(back, play, forward, speed, loop, range, clock);
|
|
244
|
-
hint.textContent = stepMode ? w.keysStep : w.keys;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
240
|
+
hint.textContent = source.help === "turn" ? (stepMode ? w.keysTurnStep : w.keysTurn) : stepMode ? w.keysStep : w.keys;
|
|
241
|
+
if (source.labelled !== undefined) {
|
|
242
|
+
source.labelled.setAttribute("aria-label", w.region);
|
|
243
|
+
source.labelled.setAttribute("aria-describedby", hint.id);
|
|
244
|
+
source.labelled.setAttribute("aria-keyshortcuts", "Space ArrowLeft ArrowRight Shift+ArrowLeft Shift+ArrowRight Home End");
|
|
245
|
+
}
|
|
246
|
+
let said = "";
|
|
249
247
|
sync = () => {
|
|
250
248
|
const s = controller.state();
|
|
251
249
|
const total = replayClock(info.duration);
|
|
252
250
|
const text = `${replayClock(s.t - info.start)} / ${total}`;
|
|
253
|
-
if (text !==
|
|
254
|
-
|
|
251
|
+
if (text !== said) {
|
|
252
|
+
said = text;
|
|
255
253
|
clock.textContent = text;
|
|
256
254
|
range.setAttribute("aria-valuetext", fill(w.positionValue, { t: replaySpoken(s.t - info.start, w), total: replaySpoken(info.duration, w) }));
|
|
257
255
|
}
|
|
@@ -267,79 +265,101 @@ export function createReplayView(host, timeline, settings, initial) {
|
|
|
267
265
|
if (speed.value !== String(s.speed))
|
|
268
266
|
speed.value = String(s.speed);
|
|
269
267
|
loop.setAttribute("aria-pressed", String(s.loop));
|
|
270
|
-
|
|
271
|
-
|
|
268
|
+
source.holder.dataset.state = s.ended ? "ended" : s.playing ? "playing" : "paused";
|
|
269
|
+
source.holder.dataset.motion = s.motion;
|
|
272
270
|
};
|
|
273
271
|
sync(undefined);
|
|
274
272
|
};
|
|
275
|
-
|
|
276
|
-
bar.hidden = true;
|
|
277
|
-
// ---- The clock on the page: animation frames while playing, and the events said aloud. ----
|
|
273
|
+
// ---- The clock on the page: animation frames while playing and in sight, and the events said aloud. ----
|
|
278
274
|
let raf = 0;
|
|
279
275
|
let last = 0;
|
|
280
|
-
|
|
276
|
+
let onScreen = true;
|
|
277
|
+
const request = source.frames?.request ?? ((callback) => requestAnimationFrame(callback));
|
|
278
|
+
const cancel = source.frames?.cancel ?? ((id) => cancelAnimationFrame(id));
|
|
279
|
+
const want = () => {
|
|
280
|
+
if (raf !== 0 || !onScreen || !controller.state().playing)
|
|
281
|
+
return;
|
|
282
|
+
last = performance.now();
|
|
283
|
+
raf = request(frame);
|
|
284
|
+
};
|
|
285
|
+
function frame(now) {
|
|
281
286
|
raf = 0;
|
|
282
|
-
if (!controller.state().playing)
|
|
287
|
+
if (!controller.state().playing || !onScreen)
|
|
283
288
|
return;
|
|
284
289
|
const dt = Math.min(0.25, Math.max(0, (now - last) / 1000));
|
|
285
290
|
last = now;
|
|
286
291
|
controller.advance(dt);
|
|
287
|
-
if (controller.state().playing && raf === 0)
|
|
288
|
-
raf =
|
|
289
|
-
}
|
|
292
|
+
if (controller.state().playing && raf === 0 && onScreen)
|
|
293
|
+
raf = request(frame);
|
|
294
|
+
}
|
|
290
295
|
const stop = controller.subscribe((change) => {
|
|
291
296
|
sync(change);
|
|
292
|
-
paint(change.state.t);
|
|
297
|
+
source.paint(change.state.t);
|
|
293
298
|
if (settings.announce !== false && change.crossed.length > 0 && (change.reason === "tick" || change.reason === "step")) {
|
|
294
299
|
live.textContent = change.crossed.map((e) => describeEvent(e, language, info.start)).join(". ");
|
|
295
300
|
}
|
|
296
|
-
if (change.reason === "play"
|
|
297
|
-
|
|
298
|
-
raf = requestAnimationFrame(frame);
|
|
299
|
-
}
|
|
301
|
+
if (change.reason === "play")
|
|
302
|
+
want();
|
|
300
303
|
if (!change.state.playing && raf !== 0) {
|
|
301
|
-
|
|
304
|
+
cancel(raf);
|
|
302
305
|
raf = 0;
|
|
303
306
|
}
|
|
304
307
|
});
|
|
305
|
-
|
|
308
|
+
// Scrolled out of sight, the clock holds (no frame is asked for) and goes on when it is back.
|
|
309
|
+
let seen;
|
|
310
|
+
if (source.seen !== undefined && typeof IntersectionObserver === "function") {
|
|
311
|
+
seen = new IntersectionObserver((entries) => {
|
|
312
|
+
onScreen = entries[entries.length - 1]?.isIntersecting !== false;
|
|
313
|
+
if (onScreen)
|
|
314
|
+
want();
|
|
315
|
+
else if (raf !== 0) {
|
|
316
|
+
cancel(raf);
|
|
317
|
+
raf = 0;
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
seen.observe(source.seen);
|
|
321
|
+
}
|
|
322
|
+
const act = (event, scope) => {
|
|
306
323
|
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey)
|
|
307
|
-
return;
|
|
324
|
+
return false;
|
|
308
325
|
const tag = event.target?.tagName ?? "";
|
|
309
326
|
if (tag === "INPUT" || tag === "SELECT" || tag === "TEXTAREA")
|
|
310
|
-
return;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
327
|
+
return false;
|
|
328
|
+
// Over a 3D view the arrows and Home are the view's: the stage's own keys are Space, comma, full stop and End.
|
|
329
|
+
const key = scope === "stage" ? (event.code === "Comma" ? "back" : event.code === "Period" ? "on" : event.key === " " || event.key === "Spacebar" ? "toggle" : event.key === "End" ? "end" : "") : event.key === " " || event.key === "Spacebar" ? "toggle" : event.key === "ArrowRight" ? "on" : event.key === "ArrowLeft" ? "back" : event.key === "Home" ? "home" : event.key === "End" ? "end" : "";
|
|
330
|
+
switch (key) {
|
|
331
|
+
case "toggle":
|
|
314
332
|
// A button takes the space itself.
|
|
315
333
|
if (tag === "BUTTON")
|
|
316
|
-
return;
|
|
334
|
+
return false;
|
|
317
335
|
controller.toggle();
|
|
318
336
|
break;
|
|
319
|
-
case "
|
|
337
|
+
case "on":
|
|
320
338
|
if (event.shiftKey)
|
|
321
339
|
controller.jump(5);
|
|
322
340
|
else
|
|
323
341
|
controller.step(1);
|
|
324
342
|
break;
|
|
325
|
-
case "
|
|
343
|
+
case "back":
|
|
326
344
|
if (event.shiftKey)
|
|
327
345
|
controller.jump(-5);
|
|
328
346
|
else
|
|
329
347
|
controller.step(-1);
|
|
330
348
|
break;
|
|
331
|
-
case "
|
|
349
|
+
case "home":
|
|
332
350
|
controller.seek(info.start);
|
|
333
351
|
break;
|
|
334
|
-
case "
|
|
352
|
+
case "end":
|
|
335
353
|
controller.seek(info.end);
|
|
336
354
|
break;
|
|
337
355
|
default:
|
|
338
|
-
return;
|
|
356
|
+
return false;
|
|
339
357
|
}
|
|
340
358
|
event.preventDefault();
|
|
359
|
+
return true;
|
|
341
360
|
};
|
|
342
|
-
|
|
361
|
+
const keys = (event) => void act(event, "bar");
|
|
362
|
+
source.keysOn.addEventListener("keydown", keys);
|
|
343
363
|
// A page that is hidden does no work: the clock stops, and the reader starts it again.
|
|
344
364
|
const hidden = () => {
|
|
345
365
|
if (doc.visibilityState === "hidden")
|
|
@@ -357,31 +377,84 @@ export function createReplayView(host, timeline, settings, initial) {
|
|
|
357
377
|
media.addListener(motion);
|
|
358
378
|
}
|
|
359
379
|
buildBar();
|
|
360
|
-
drawStage(controller.state().t);
|
|
361
|
-
if (settings.autoplay === true && controller.state().motion === "smooth" && doc.visibilityState !== "hidden")
|
|
362
|
-
controller.play();
|
|
363
380
|
return {
|
|
364
381
|
controller,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
language =
|
|
370
|
-
|
|
371
|
-
if (language !== before)
|
|
372
|
-
buildBar();
|
|
373
|
-
drawStage(controller.state().t);
|
|
382
|
+
parts: { style, hint, bar, live },
|
|
383
|
+
setLanguage(next) {
|
|
384
|
+
if (next === language)
|
|
385
|
+
return;
|
|
386
|
+
language = next;
|
|
387
|
+
buildBar();
|
|
374
388
|
},
|
|
389
|
+
key: act,
|
|
375
390
|
destroy() {
|
|
376
391
|
stop();
|
|
377
392
|
if (raf !== 0)
|
|
378
|
-
|
|
393
|
+
cancel(raf);
|
|
394
|
+
raf = 0;
|
|
395
|
+
seen?.disconnect();
|
|
396
|
+
source.keysOn.removeEventListener("keydown", keys);
|
|
379
397
|
doc.removeEventListener("visibilitychange", hidden);
|
|
380
398
|
if (typeof media?.removeEventListener === "function")
|
|
381
399
|
media.removeEventListener("change", motion);
|
|
382
400
|
else if (typeof media?.removeListener === "function")
|
|
383
401
|
media.removeListener(motion);
|
|
384
402
|
controller.pause();
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Attach a replay to a container: the stage with the drawing, the controls under it, the keyboard and the live region. The drawing is written once
|
|
408
|
+
* and then only the marks that move are moved, on `requestAnimationFrame`; a mark that comes or goes (an object that enters, an event that
|
|
409
|
+
* begins) redraws the replay's own layers, never the surface. With `prefers-reduced-motion` nothing plays by itself and play steps instead.
|
|
410
|
+
*/
|
|
411
|
+
export function createReplayView(host, timeline, settings, initial) {
|
|
412
|
+
const doc = host.ownerDocument;
|
|
413
|
+
const id = initial.id ?? `ky-r${++counter}`;
|
|
414
|
+
let options = { ...initial, id };
|
|
415
|
+
let language = options.language === "ja" ? "ja" : "en";
|
|
416
|
+
const root = doc.createElement("div");
|
|
417
|
+
root.className = "ky-replay";
|
|
418
|
+
const stage = doc.createElement("div");
|
|
419
|
+
stage.className = "ky-stage";
|
|
420
|
+
stage.tabIndex = 0;
|
|
421
|
+
stage.setAttribute("role", "group");
|
|
422
|
+
let svg = null;
|
|
423
|
+
const painter = replayPainter({
|
|
424
|
+
doc,
|
|
425
|
+
timeline,
|
|
426
|
+
look: settings,
|
|
427
|
+
svg: () => svg,
|
|
428
|
+
options: () => options,
|
|
429
|
+
language: () => language,
|
|
430
|
+
redraw: (t) => drawStage(t),
|
|
431
|
+
});
|
|
432
|
+
function drawStage(t) {
|
|
433
|
+
stage.innerHTML = drawFrame(options, timeline, t, settings);
|
|
434
|
+
svg = stage.querySelector("svg.kyogijo");
|
|
435
|
+
}
|
|
436
|
+
const driver = replayDriver(doc, timeline, settings, { paint: (t) => painter.paint(t), language, keysOn: root, holder: root, labelled: stage });
|
|
437
|
+
const { style, hint, bar, live } = driver.parts;
|
|
438
|
+
root.append(style, stage, hint, bar, live);
|
|
439
|
+
host.replaceChildren(root);
|
|
440
|
+
drawStage(driver.controller.state().t);
|
|
441
|
+
painter.bind(driver.controller.state().t);
|
|
442
|
+
if (settings.autoplay === true && driver.controller.state().motion === "smooth" && doc.visibilityState !== "hidden")
|
|
443
|
+
driver.controller.play();
|
|
444
|
+
return {
|
|
445
|
+
controller: driver.controller,
|
|
446
|
+
stage,
|
|
447
|
+
draw(next) {
|
|
448
|
+
options = { ...next, id };
|
|
449
|
+
language = options.language === "ja" ? "ja" : "en";
|
|
450
|
+
// The controls are made again only when the words change: a reader's focus on one of them stays where it is when a score moves.
|
|
451
|
+
driver.setLanguage(language);
|
|
452
|
+
const t = driver.controller.state().t;
|
|
453
|
+
drawStage(t);
|
|
454
|
+
painter.bind(t);
|
|
455
|
+
},
|
|
456
|
+
destroy() {
|
|
457
|
+
driver.destroy();
|
|
385
458
|
host.replaceChildren();
|
|
386
459
|
},
|
|
387
460
|
};
|
package/dist/replay.js
CHANGED
|
@@ -160,6 +160,11 @@ export function replayInfo(timeline) {
|
|
|
160
160
|
const p = prepare(timeline);
|
|
161
161
|
return { start: p.start, end: p.end, duration: p.end - p.start, frames: p.frames.length, objects: p.ids.length, events: p.events.length, step: p.step };
|
|
162
162
|
}
|
|
163
|
+
/** Everything a timeline tracks, each as it first appears (its id, team, number and label), in that order. */
|
|
164
|
+
export function replayCast(timeline) {
|
|
165
|
+
const p = prepare(timeline);
|
|
166
|
+
return p.ids.map((id) => p.first.get(id));
|
|
167
|
+
}
|
|
163
168
|
/** The times of the frames, and of the events, in order: the moments a step can land on. */
|
|
164
169
|
export function replayTimes(timeline) {
|
|
165
170
|
return prepare(timeline).times;
|
|
@@ -541,10 +546,14 @@ export function replayPlacer(options, timeline) {
|
|
|
541
546
|
* ```
|
|
542
547
|
*/
|
|
543
548
|
export function drawFrame(options, timeline, t, look = {}) {
|
|
549
|
+
return drawFrameOf(options, timeline, t, look);
|
|
550
|
+
}
|
|
551
|
+
/** `drawFrame` with only the groups `keep` says yes to: a 3D view keeps the players off its floor, where they stand up instead. */
|
|
552
|
+
export function drawFrameOf(options, timeline, t, look = {}, keep = () => true) {
|
|
544
553
|
const language = options.language === "ja" ? "ja" : "en";
|
|
545
554
|
const id = options.id ?? replayId(options, timeline);
|
|
546
555
|
const withId = { ...options, id };
|
|
547
|
-
const layers =
|
|
556
|
+
const layers = replayGroups(timeline, replaySnapshot(timeline, t, look), look, language).filter(keep).map((group) => group.layer);
|
|
548
557
|
let description = options.description;
|
|
549
558
|
if (description === undefined) {
|
|
550
559
|
// The drawing's own description is made from its layers' summaries; a replay's layers change with the time, so its sentence is fixed.
|
package/dist/strings.core.d.ts
CHANGED
|
@@ -1006,6 +1006,9 @@ export interface KyogijoWords {
|
|
|
1006
1006
|
/** What the replay is and the keys it answers to, in smooth motion and in steps. */
|
|
1007
1007
|
keys: string;
|
|
1008
1008
|
keysStep: string;
|
|
1009
|
+
/** The same on a 3D view, where the arrow keys turn the view: in smooth motion and in steps. */
|
|
1010
|
+
keysTurn: string;
|
|
1011
|
+
keysTurnStep: string;
|
|
1009
1012
|
region: string;
|
|
1010
1013
|
};
|
|
1011
1014
|
/** The words of the 3D view: its name, the keys, the viewpoints, the controls, what is announced, and the names of what stands on the surface. */
|
package/dist/strings.en.js
CHANGED
|
@@ -444,6 +444,8 @@ export const WORDS_EN = {
|
|
|
444
444
|
group: "Replay controls", play: "Play", pause: "Pause", back: "Previous frame", forward: "Next frame", stepBack: "Back", stepForward: "Forward", speed: "Speed", loop: "Loop", position: "Position", time: "Time", event: "Event", spokenSeconds: "0:{ss}", spokenMinutes: "{m}:{ss}", spokenHours: "{h}:{mm}:{ss}", positionValue: "{t} of {total}",
|
|
445
445
|
keys: "Replay of the play. Space plays or pauses, the left and right arrow keys step by a frame, Shift with an arrow key moves 5 seconds, and Home and End go to the ends.",
|
|
446
446
|
keysStep: "Replay of the play, in steps. Space or the right arrow key moves on, the left arrow key goes back, and Home and End go to the ends.",
|
|
447
|
+
keysTurn: "Replay of the play on the 3D view. Space plays or pauses; comma and full stop step back and on by a frame, and with Shift by 5 seconds; End goes to the end. The arrow keys still turn the view. On the replay's controls the arrow keys step, and Home and End go to the ends.",
|
|
448
|
+
keysTurnStep: "Replay of the play on the 3D view, in steps. Space or full stop moves on, comma goes back, and End goes to the end. The arrow keys still turn the view. On the replay's controls the right arrow key moves on and the left goes back.",
|
|
447
449
|
region: "Replay",
|
|
448
450
|
},
|
|
449
451
|
turn: {
|
package/dist/strings.ja.js
CHANGED
|
@@ -469,6 +469,8 @@ export const WORDS_JA = {
|
|
|
469
469
|
group: "リプレイの操作", play: "再生", pause: "一時停止", back: "前のコマ", forward: "次のコマ", stepBack: "前へ", stepForward: "次へ", speed: "再生速度", loop: "繰り返し", position: "再生位置", time: "時間", event: "イベント", spokenSeconds: "{s}秒", spokenMinutes: "{m}分{s}秒", spokenHours: "{h}時間{m}分{s}秒", positionValue: "{total}中{t}",
|
|
470
470
|
keys: "プレーのリプレイです。スペースキーで再生と一時停止、左右の矢印キーで1コマずつ前後に移動、Shiftキーを押しながら矢印キーで5秒ずつ移動、Homeキーで最初、Endキーで最後に移動します。",
|
|
471
471
|
keysStep: "プレーのリプレイです。コマ送りで表示しています。スペースキーまたは右矢印キーで進み、左矢印キーで戻り、Homeキーで最初、Endキーで最後に移動します。",
|
|
472
|
+
keysTurn: "3Dビューでのプレーのリプレイです。スペースキーで再生と一時停止、カンマキーで1コマ戻り、ピリオドキーで1コマ進みます。Shiftキーを押しながらカンマキーかピリオドキーで5秒ずつ移動し、Endキーで最後に移動します。矢印キーではこれまでどおりビューを回せます。リプレイの操作ボタンにフォーカスがあるときは、左右の矢印キーで1コマずつ前後に移動し、Homeキーで最初、Endキーで最後に移動します。",
|
|
473
|
+
keysTurnStep: "3Dビューでのプレーのリプレイです。コマ送りで表示しています。スペースキーまたはピリオドキーで進み、カンマキーで戻り、Endキーで最後に移動します。矢印キーではこれまでどおりビューを回せます。リプレイの操作ボタンにフォーカスがあるときは、右矢印キーで進み、左矢印キーで戻ります。",
|
|
472
474
|
region: "リプレイ",
|
|
473
475
|
},
|
|
474
476
|
turn: {
|
package/dist/turn-entry.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import "./full.ts";
|
|
|
2
2
|
import "./venues.all.ts";
|
|
3
3
|
export { mountKyogijoTurn, mountTurn, turnSupported } from "./turn.dom.ts";
|
|
4
4
|
export type { KyogijoTurn } from "./turn.dom.ts";
|
|
5
|
+
export { discSize, keepOnFloor, replayTurnModel } from "./turn.replay.ts";
|
|
6
|
+
export type { TurnReplayLook, TurnReplayModel } from "./turn.replay.ts";
|
|
7
|
+
export type { TurnReplaySettings } from "./turn.replay.dom.ts";
|
|
8
|
+
import "./turn.replay.dom.ts";
|
|
5
9
|
export { buildTurn, floorOptions, openingView } from "./turn.model.ts";
|
|
6
10
|
export { turnDirection } from "./turn.camera.ts";
|
|
7
11
|
export type { TurnDirection } from "./turn.camera.ts";
|
package/dist/turn-entry.js
CHANGED
|
@@ -3,6 +3,8 @@ import "./full.js";
|
|
|
3
3
|
// The real venues of the NHL's arenas, as this entry has always had them (the lean district reads the ones it shows).
|
|
4
4
|
import "./venues.all.js";
|
|
5
5
|
export { mountKyogijoTurn, mountTurn, turnSupported } from "./turn.dom.js";
|
|
6
|
+
export { discSize, keepOnFloor, replayTurnModel } from "./turn.replay.js";
|
|
7
|
+
import "./turn.replay.dom.js";
|
|
6
8
|
export { buildTurn, floorOptions, openingView } from "./turn.model.js";
|
|
7
9
|
export { turnDirection } from "./turn.camera.js";
|
|
8
10
|
export { GOVERNOR_TUNING, QUALITIES, QUALITY_TIERS, TIER_PROFILES, createGovernor, handheld, isQuality, qualityOf, rasterOf, tierOf } from "./quality.js";
|
package/dist/turn.dom.d.ts
CHANGED
|
@@ -4,6 +4,20 @@ import type { ScoreOptions, SurfaceOptions } from "./types.ts";
|
|
|
4
4
|
import type { VenueSettings } from "./venue.ts";
|
|
5
5
|
import { type Quality, type QualityTier } from "./quality.ts";
|
|
6
6
|
import type { OutsideLevel } from "./outside.paint.ts";
|
|
7
|
+
import type { ReplaySettings, ReplayTimeline } from "./replay.types.ts";
|
|
8
|
+
import type { KyogijoReplay } from "./mount.ts";
|
|
9
|
+
import type { TurnInternals } from "./turn.replay.dom.ts";
|
|
10
|
+
import type { ReplayView } from "./replay.dom.ts";
|
|
11
|
+
/**
|
|
12
|
+
* The replay's own code, which `turn.replay.dom.ts` puts in when it is loaded (the `/turn` entry and the element load it): a page that only turns a view (a district, a tournament layout) carries none of the replay
|
|
13
|
+
* and its layers.
|
|
14
|
+
*/
|
|
15
|
+
declare let replayKit: {
|
|
16
|
+
attach(view: TurnInternals, timeline: ReplayTimeline, settings: ReplaySettings): KyogijoReplay;
|
|
17
|
+
flat(host: HTMLElement, timeline: ReplayTimeline, settings: ReplaySettings, options: SurfaceOptions | ScoreOptions): ReplayView;
|
|
18
|
+
} | undefined;
|
|
19
|
+
/** Put the replay's code in: called once by `turn.replay.dom.ts`. */
|
|
20
|
+
export declare function useTurnReplay(kit: NonNullable<typeof replayKit>): void;
|
|
7
21
|
/** What `mountKyogijoTurn` gives back. `O` is what `update` takes: the options of a drawing, or (for `mountMultiTurn`) of a tournament layout. */
|
|
8
22
|
export interface KyogijoTurn<O = SurfaceOptions | ScoreOptions> {
|
|
9
23
|
/** `3d`, or `flat` where the browser cannot draw the 3D view (or there is nothing to turn): the flat drawing is shown and the rest of this does nothing. */
|
|
@@ -52,6 +66,12 @@ export interface KyogijoTurn<O = SurfaceOptions | ScoreOptions> {
|
|
|
52
66
|
setQuality(quality: Quality): void;
|
|
53
67
|
/** The flat drawing that is the floor, or `null` after `destroy`. */
|
|
54
68
|
svg(): SVGSVGElement | null;
|
|
69
|
+
/**
|
|
70
|
+
* Play tracking data on the view: the players stand up on the surface as discs that face the eye (home circles, away squares, numbers), the ball or the puck runs on the floor with its trail, and the passes, shots and
|
|
71
|
+
* goals are drawn on the floor as in the flat replay. The replay's controls go under the view's; the view still turns, tips and zooms while it plays. Space on the view plays and pauses, comma and full stop step; the
|
|
72
|
+
* arrow keys still turn the view. `destroy` on what it gives takes the replay away and leaves the view. A tournament layout has no replay. Where the browser cannot draw the 3D view, the flat replay plays instead.
|
|
73
|
+
*/
|
|
74
|
+
replay(timeline: ReplayTimeline, settings?: ReplaySettings): KyogijoReplay;
|
|
55
75
|
/** Stop listening and empty the container. */
|
|
56
76
|
destroy(): void;
|
|
57
77
|
}
|
|
@@ -77,4 +97,5 @@ export declare function mountKyogijoTurn(host: HTMLElement, options: SurfaceOpti
|
|
|
77
97
|
* Mount any 3D model (`mountKyogijoTurn` is this with the model of one surface; `mountMultiTurn` with a tournament layout's): `build` makes the model from what `update` is given, and the rest, the controls, the pointer, the keys
|
|
78
98
|
* and the auto-turn, is the same.
|
|
79
99
|
*/
|
|
80
|
-
export declare function mountTurn<O>(host: HTMLElement, options: O, settings: TurnSettings, build: (options: O, settings: TurnSettings) => TurnModel): KyogijoTurn<O>;
|
|
100
|
+
export declare function mountTurn<O>(host: HTMLElement, options: O, settings: TurnSettings, build: (options: O, settings: TurnSettings) => TurnModel, replayable?: boolean): KyogijoTurn<O>;
|
|
101
|
+
export {};
|