@simon-says-build/demo-player 0.8.0 → 0.8.2
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/package.json +1 -1
- package/src/DemoHost.js +28 -8
- package/src/core.js +6 -1
- package/src/page-name.js +23 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simon-says-build/demo-player",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "SimonSays mobile demo player: walks a preview's viewer through a job's change, inside the app. Renders nothing unless the bundle carries a demo.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/src/DemoHost.js
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
// No JSX, so it needs nothing from the app's Babel setup beyond React Native's.
|
|
15
15
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
16
16
|
import { Linking, Pressable, ScrollView, StyleSheet, Text, TextInput, View, useWindowDimensions } from 'react-native';
|
|
17
|
-
import {
|
|
17
|
+
import { pageName } from './page-name.js';
|
|
18
|
+
import { cardBox, currentDemo, demoConfig, findTarget, handlerFor, onDemo, phoneSteps, playDemo, releaseOf, reviewChangesOf, scalePoint, stepTitle } from './core.js';
|
|
18
19
|
import { demoApi, tourOf } from './api.js';
|
|
19
20
|
import { signInFirst, openInApp } from './signin.js';
|
|
20
21
|
import { runEvent, fillStep } from './events.js';
|
|
@@ -78,6 +79,7 @@ function Player({ config, router, insets, onClose, children }) {
|
|
|
78
79
|
const [signIn, setSignIn] = useState(null); // a `signin` step: null while it runs, else { ok, error }
|
|
79
80
|
const [event, setEvent] = useState(null); // an `event` step: null while it runs, else { ok, error }
|
|
80
81
|
const [vars, setVars] = useState({}); // per person: what their events set up ({walkId} in later steps)
|
|
82
|
+
const [flash, setFlash] = useState(null); // "Now navigating to the walk page", said before a goto opens it
|
|
81
83
|
|
|
82
84
|
useEffect(() => {
|
|
83
85
|
let live = true;
|
|
@@ -125,15 +127,27 @@ function Player({ config, router, insets, onClose, children }) {
|
|
|
125
127
|
api.progress({ status: 'done' }).catch(() => {});
|
|
126
128
|
}, [api]);
|
|
127
129
|
|
|
130
|
+
const advance = useCallback(() => {
|
|
131
|
+
if (index + 1 >= steps.length) { if (changes.length) setReviewing(true); else finish(); }
|
|
132
|
+
else setIndex(index + 1);
|
|
133
|
+
}, [steps, index, finish, changes]);
|
|
134
|
+
// SAID BEFORE IT GOES (2026-11-01, Michael): a goto flashes "Now navigating
|
|
135
|
+
// to the walk page" for a moment, then opens it (schemas/page-name.mjs).
|
|
128
136
|
const next = useCallback(() => {
|
|
129
|
-
if (!steps) return;
|
|
137
|
+
if (!steps || flash) return;
|
|
130
138
|
if (step?.kind === 'goto' && step.path) {
|
|
131
|
-
|
|
132
|
-
|
|
139
|
+
const path = step.path;
|
|
140
|
+
setFlash(`Now navigating to ${pageName(path)}`);
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
if (path.startsWith('/') && router?.push) router.push(path);
|
|
143
|
+
else Linking.openURL(path).catch(() => {});
|
|
144
|
+
setFlash(null);
|
|
145
|
+
advance();
|
|
146
|
+
}, FLASH_MS);
|
|
147
|
+
return;
|
|
133
148
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}, [steps, step, index, router, finish, changes]);
|
|
149
|
+
advance();
|
|
150
|
+
}, [steps, step, router, advance, flash]);
|
|
137
151
|
|
|
138
152
|
const doIt = useCallback(() => {
|
|
139
153
|
if (!step || !found) return;
|
|
@@ -183,6 +197,8 @@ function Player({ config, router, insets, onClose, children }) {
|
|
|
183
197
|
if (error) return h(Card, { at: null, screen, insets, card, setCard, title: 'Demo', body: `The demo didn't load: ${error}` });
|
|
184
198
|
if (!steps) return null;
|
|
185
199
|
if (reviewing && !tucked) return h(Review, { api, changes, release, screen, insets, onBack: steps.length ? () => setReviewing(false) : null, onDone: finish, onHide: () => setTucked(true) });
|
|
200
|
+
if (flash) return h(View, { pointerEvents: 'none', style: [s.flash, { top: insets.top + screen.height * 0.4 }], testID: 'ssb-demo-flash' },
|
|
201
|
+
h(Text, { style: s.flashText, accessibilityLiveRegion: 'polite' }, flash));
|
|
186
202
|
if (tucked || done) {
|
|
187
203
|
// A demo started at run time closes when it is done (▶ Demo again replays it);
|
|
188
204
|
// one baked into a preview stays, to be replayed from its pill.
|
|
@@ -212,7 +228,7 @@ function Player({ config, router, insets, onClose, children }) {
|
|
|
212
228
|
rect ? h(View, { pointerEvents: 'none', style: [s.ring, { left: rect.left - 6, top: rect.top - 6, width: rect.width + 12, height: rect.height + 12 }] }) : null,
|
|
213
229
|
h(Card, {
|
|
214
230
|
at: rect, place: step.place, screen, insets, card, setCard,
|
|
215
|
-
title:
|
|
231
|
+
title: stepTitle(step, index, steps.length), body: step.caption,
|
|
216
232
|
actions: [
|
|
217
233
|
{ label: 'Hide', onPress: () => setTucked(true) },
|
|
218
234
|
index > 0 && steps[index - 1].kind !== 'signin' ? { label: 'Back', onPress: () => setIndex(index - 1) } : null,
|
|
@@ -307,6 +323,8 @@ function Card({ at, place, screen, insets, card, setCard, title, body, actions =
|
|
|
307
323
|
}, h(Text, { style: [s.buttonText, a.primary && s.primaryText] }, a.label)))) : null);
|
|
308
324
|
}
|
|
309
325
|
|
|
326
|
+
const FLASH_MS = 1100; // long enough to read "Now navigating to the walk page"
|
|
327
|
+
|
|
310
328
|
const s = StyleSheet.create({
|
|
311
329
|
root: { flex: 1 },
|
|
312
330
|
ring: { position: 'absolute', borderRadius: 12, borderWidth: 3, borderColor: '#FFFFFF', shadowColor: '#000', shadowOpacity: 0.6, shadowRadius: 8, shadowOffset: { width: 0, height: 0 } },
|
|
@@ -327,6 +345,8 @@ const s = StyleSheet.create({
|
|
|
327
345
|
changeState: { color: '#AEAEB2', fontSize: 13, marginTop: 4 },
|
|
328
346
|
error: { color: '#FF6961', fontSize: 13, marginTop: 4 },
|
|
329
347
|
input: { marginTop: 10, minHeight: 64, padding: 10, borderRadius: 10, backgroundColor: '#2C2C2E', color: '#FFFFFF', fontSize: 15 },
|
|
348
|
+
flash: { position: 'absolute', left: 32, right: 32, alignItems: 'center', paddingVertical: 14, paddingHorizontal: 18, borderRadius: 14, backgroundColor: 'rgba(28,28,30,0.92)' },
|
|
349
|
+
flashText: { color: '#FFFFFF', fontSize: 16, fontWeight: '600', textAlign: 'center' },
|
|
330
350
|
pill: { position: 'absolute', right: 16, paddingVertical: 8, paddingHorizontal: 14, borderRadius: 18, backgroundColor: '#1C1C1E' },
|
|
331
351
|
pillText: { color: '#FFFFFF', fontSize: 13, fontWeight: '600' },
|
|
332
352
|
});
|
package/src/core.js
CHANGED
|
@@ -10,7 +10,7 @@ import { bubbleBox } from './step-place.js';
|
|
|
10
10
|
// What this player is, said when the app registers for "Ready to see"
|
|
11
11
|
// (packages/ssb-overlay sends it; functions/in-app-notify.mjs clientOf). Kept
|
|
12
12
|
// equal to package.json by test/core.test.mjs.
|
|
13
|
-
export const PLAYER_VERSION = '0.8.
|
|
13
|
+
export const PLAYER_VERSION = '0.8.2';
|
|
14
14
|
|
|
15
15
|
export const HOST = 5;
|
|
16
16
|
export const TEXT = 6;
|
|
@@ -103,6 +103,11 @@ export function scalePoint(point, screen, from = RECORDED_SCREEN) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/** A step card's title: where it is in the demo, and whether SSB only set it up (a best-effort step: the state seeded and the screen opened, the change not driven). */
|
|
107
|
+
export function stepTitle(step, index, total) {
|
|
108
|
+
return `${index + 1} of ${total}${step?.bestEffort === true ? ' · set up by SSB' : ''}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
106
111
|
/** The steps of a tour a phone can play, in order. A step with no caption says nothing and is dropped. */
|
|
107
112
|
export function phoneSteps(steps) {
|
|
108
113
|
return (steps || []).filter((s) => s && PHONE_KINDS.has(s.kind) && String(s.caption || '').trim());
|
package/src/page-name.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// THE PAGE A STEP OPENS, in words (2026-11-01, Michael: "before any navigation,
|
|
2
|
+
// can we show a brief flash showing 'Now navigating to [the X page]'"). Both
|
|
3
|
+
// players say it before they navigate: the phone's (packages/demo-player/src/
|
|
4
|
+
// page-name.js) and the extension's (extension/overlay.js) carry copies of
|
|
5
|
+
// pageName, held equal by bin/page-name.test.mjs. Pure.
|
|
6
|
+
//
|
|
7
|
+
// /walk/{walkId} → the walk page (an id or {placeholder} is not a name)
|
|
8
|
+
// /(app)/settings/profile → the profile page (an Expo Router group is not a name)
|
|
9
|
+
// /MainStack/Tabs/Dashboard → the dashboard page (a React Navigation chain)
|
|
10
|
+
// walk2gether://walk/abc → the walk page (a deep link)
|
|
11
|
+
// / → the home page
|
|
12
|
+
export function pageName(path) {
|
|
13
|
+
// A web address drops its host; a deep link's "host" is its first screen.
|
|
14
|
+
const rest = String(path || '').replace(/^https?:\/\/[^/]*/i, '').replace(/^[a-z][a-z0-9+.-]*:\/\//i, '/').split(/[?#]/)[0];
|
|
15
|
+
const isId = (s) => /^\{.*\}$/.test(s) || /^\[.*\]$/.test(s) || /^:/.test(s) || /^\(.*\)$/.test(s) || s === 'index'
|
|
16
|
+
|| /^[0-9]+$/.test(s) || /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(s) || (s.length >= 16 && /[0-9]/.test(s) && /^[A-Za-z0-9_-]+$/.test(s));
|
|
17
|
+
const parts = rest.split('/').filter(Boolean).map((s) => { try { return decodeURIComponent(s); } catch { return s; } }).filter((s) => !isId(s));
|
|
18
|
+
const last = parts[parts.length - 1];
|
|
19
|
+
if (!last) return 'the home page';
|
|
20
|
+
const words = last.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/[-_.]+/g, ' ').trim().toLowerCase();
|
|
21
|
+
return `the ${words || 'home'} page`;
|
|
22
|
+
}
|
|
23
|
+
// END pageName
|