@seriousgaming/sdk 0.0.0-stage → 1.0.0-alpha.1
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/LICENSE +202 -0
- package/NOTICE +1 -0
- package/README.md +29 -2
- package/lib/build/callbacks.d.ts +998 -0
- package/lib/build/callbacks.js +32 -0
- package/lib/build/context-merge.d.ts +59 -0
- package/lib/build/context-merge.js +118 -0
- package/lib/build/document.d.ts +36 -0
- package/lib/build/document.js +8 -0
- package/lib/build/encoding.d.ts +26 -0
- package/lib/build/encoding.js +78 -0
- package/lib/build/endpoints.d.ts +421 -0
- package/lib/build/endpoints.js +46 -0
- package/lib/build/hooks.d.ts +74 -0
- package/lib/build/hooks.js +1 -0
- package/lib/build/index.d.ts +18 -0
- package/lib/build/index.js +16 -0
- package/lib/build/llm.d.ts +166 -0
- package/lib/build/llm.js +17 -0
- package/lib/build/resource-schema.d.ts +1048 -0
- package/lib/build/resource-schema.js +7 -0
- package/lib/build/scheduling.d.ts +112 -0
- package/lib/build/scheduling.js +217 -0
- package/lib/build/seed.d.ts +51 -0
- package/lib/build/seed.js +89 -0
- package/lib/build/signatures.d.ts +372 -0
- package/lib/build/signatures.js +1 -0
- package/lib/build/speech.d.ts +102 -0
- package/lib/build/speech.js +11 -0
- package/lib/build/tick-validation.d.ts +23 -0
- package/lib/build/tick-validation.js +96 -0
- package/lib/build/timer.d.ts +74 -0
- package/lib/build/timer.js +1 -0
- package/lib/build/validators.d.ts +116 -0
- package/lib/build/validators.js +1 -0
- package/lib/commands.d.ts +357 -0
- package/lib/commands.js +14 -0
- package/lib/e2e.d.ts +238 -0
- package/lib/e2e.js +248 -0
- package/lib/projection.d.ts +266 -0
- package/lib/projection.js +23 -0
- package/lib/testing/index.d.ts +13 -0
- package/lib/testing/index.js +13 -0
- package/lib/testing/logic-args.d.ts +177 -0
- package/lib/testing/logic-args.js +229 -0
- package/lib/testing/stub-services.d.ts +41 -0
- package/lib/testing/stub-services.js +176 -0
- package/lib/ui/CrossTeamDataContext.d.ts +36 -0
- package/lib/ui/CrossTeamDataContext.js +32 -0
- package/lib/ui/PresentationContext.d.ts +72 -0
- package/lib/ui/PresentationContext.js +13 -0
- package/lib/ui/PresentationWrapper.d.ts +12 -0
- package/lib/ui/PresentationWrapper.js +78 -0
- package/lib/ui/SessionUserInterfaceCallbacksContext.d.ts +3 -0
- package/lib/ui/SessionUserInterfaceCallbacksContext.js +7 -0
- package/lib/ui/SessionUserInterfaceDataContext.d.ts +103 -0
- package/lib/ui/SessionUserInterfaceDataContext.js +17 -0
- package/lib/ui/SessionUserInterfaceHookFailuresContext.d.ts +44 -0
- package/lib/ui/SessionUserInterfaceHookFailuresContext.js +15 -0
- package/lib/ui/SessionUserInterfaceHostOverlaysContext.d.ts +23 -0
- package/lib/ui/SessionUserInterfaceHostOverlaysContext.js +23 -0
- package/lib/ui/SessionUserInterfaceLocaleContext.d.ts +11 -0
- package/lib/ui/SessionUserInterfaceLocaleContext.js +13 -0
- package/lib/ui/SessionUserInterfaceMetaContext.d.ts +39 -0
- package/lib/ui/SessionUserInterfaceMetaContext.js +13 -0
- package/lib/ui/SessionUserInterfaceParticipantsContext.d.ts +12 -0
- package/lib/ui/SessionUserInterfaceParticipantsContext.js +16 -0
- package/lib/ui/SessionUserInterfaceTeamConfigurationContext.d.ts +53 -0
- package/lib/ui/SessionUserInterfaceTeamConfigurationContext.js +23 -0
- package/lib/ui/SessionUserInterfaceTeamLinksContext.d.ts +56 -0
- package/lib/ui/SessionUserInterfaceTeamLinksContext.js +12 -0
- package/lib/ui/SessionUserInterfaceTeamsContext.d.ts +13 -0
- package/lib/ui/SessionUserInterfaceTeamsContext.js +13 -0
- package/lib/ui/SessionUserInterfaceThemeContext.d.ts +9 -0
- package/lib/ui/SessionUserInterfaceThemeContext.js +7 -0
- package/lib/ui/SessionUserInterfaceThemePreferenceContext.d.ts +3 -0
- package/lib/ui/SessionUserInterfaceThemePreferenceContext.js +7 -0
- package/lib/ui/SessionUserInterfaceWalkthroughLinkContext.d.ts +42 -0
- package/lib/ui/SessionUserInterfaceWalkthroughLinkContext.js +26 -0
- package/lib/ui/SessionUserInterfaceWrapper.d.ts +8 -0
- package/lib/ui/SessionUserInterfaceWrapper.js +321 -0
- package/lib/ui/SettingsEditorCallbacksContext.d.ts +3 -0
- package/lib/ui/SettingsEditorCallbacksContext.js +7 -0
- package/lib/ui/SettingsEditorContext.d.ts +33 -0
- package/lib/ui/SettingsEditorContext.js +16 -0
- package/lib/ui/SettingsEditorLocaleContext.d.ts +13 -0
- package/lib/ui/SettingsEditorLocaleContext.js +15 -0
- package/lib/ui/SettingsEditorThemeContext.d.ts +9 -0
- package/lib/ui/SettingsEditorThemeContext.js +7 -0
- package/lib/ui/SettingsEditorThemePreferenceContext.d.ts +3 -0
- package/lib/ui/SettingsEditorThemePreferenceContext.js +7 -0
- package/lib/ui/SettingsEditorWrapper.d.ts +13 -0
- package/lib/ui/SettingsEditorWrapper.js +74 -0
- package/lib/ui/UserInterfaceCallbacksContext.d.ts +3 -0
- package/lib/ui/UserInterfaceCallbacksContext.js +7 -0
- package/lib/ui/UserInterfaceDataContext.d.ts +78 -0
- package/lib/ui/UserInterfaceDataContext.js +11 -0
- package/lib/ui/UserInterfaceLocaleContext.d.ts +13 -0
- package/lib/ui/UserInterfaceLocaleContext.js +15 -0
- package/lib/ui/UserInterfaceOwnTeamContext.d.ts +25 -0
- package/lib/ui/UserInterfaceOwnTeamContext.js +26 -0
- package/lib/ui/UserInterfaceOwnTeamJoinLinkContext.d.ts +52 -0
- package/lib/ui/UserInterfaceOwnTeamJoinLinkContext.js +13 -0
- package/lib/ui/UserInterfaceParticipantContext.d.ts +16 -0
- package/lib/ui/UserInterfaceParticipantContext.js +24 -0
- package/lib/ui/UserInterfaceParticipantsContext.d.ts +12 -0
- package/lib/ui/UserInterfaceParticipantsContext.js +13 -0
- package/lib/ui/UserInterfaceRunKindContext.d.ts +31 -0
- package/lib/ui/UserInterfaceRunKindContext.js +31 -0
- package/lib/ui/UserInterfaceTeamsContext.d.ts +12 -0
- package/lib/ui/UserInterfaceTeamsContext.js +13 -0
- package/lib/ui/UserInterfaceThemeContext.d.ts +9 -0
- package/lib/ui/UserInterfaceThemeContext.js +7 -0
- package/lib/ui/UserInterfaceThemePreferenceContext.d.ts +3 -0
- package/lib/ui/UserInterfaceThemePreferenceContext.js +7 -0
- package/lib/ui/UserInterfaceTimerContext.d.ts +10 -0
- package/lib/ui/UserInterfaceTimerContext.js +12 -0
- package/lib/ui/UserInterfaceValidationContext.d.ts +25 -0
- package/lib/ui/UserInterfaceValidationContext.js +14 -0
- package/lib/ui/UserInterfaceViewerRoleContext.d.ts +14 -0
- package/lib/ui/UserInterfaceViewerRoleContext.js +17 -0
- package/lib/ui/UserInterfaceWrapper.d.ts +8 -0
- package/lib/ui/UserInterfaceWrapper.js +219 -0
- package/lib/ui/expectedParentOrigin.d.ts +10 -0
- package/lib/ui/expectedParentOrigin.js +32 -0
- package/lib/ui/index.d.ts +50 -0
- package/lib/ui/index.js +52 -0
- package/lib/ui/internal/SurfaceGate.d.ts +19 -0
- package/lib/ui/internal/SurfaceGate.js +17 -0
- package/lib/ui/internal/SurfaceScope.d.ts +17 -0
- package/lib/ui/internal/SurfaceScope.js +16 -0
- package/lib/ui/internal/createContexts.d.ts +50 -0
- package/lib/ui/internal/createContexts.js +32 -0
- package/lib/ui/internal/document-attributes.d.ts +9 -0
- package/lib/ui/internal/document-attributes.js +87 -0
- package/lib/ui/internal/surface-timer.d.ts +3 -0
- package/lib/ui/internal/surface-timer.js +11 -0
- package/lib/ui/internal/useLatestRef.d.ts +7 -0
- package/lib/ui/internal/useLatestRef.js +11 -0
- package/lib/ui/internal/useOptimisticMerge.d.ts +19 -0
- package/lib/ui/internal/useOptimisticMerge.js +28 -0
- package/lib/ui/internal/useSurfaceControls.d.ts +52 -0
- package/lib/ui/internal/useSurfaceControls.js +19 -0
- package/lib/ui/internal/useTypedPastState.d.ts +17 -0
- package/lib/ui/internal/useTypedPastState.js +17 -0
- package/lib/ui/mutation-commit.d.ts +32 -0
- package/lib/ui/mutation-commit.js +1 -0
- package/lib/ui/mutation-outcome.d.ts +66 -0
- package/lib/ui/mutation-outcome.js +49 -0
- package/lib/ui/request-id.d.ts +18 -0
- package/lib/ui/request-id.js +44 -0
- package/lib/ui/testing.d.ts +365 -0
- package/lib/ui/testing.js +572 -0
- package/lib/ui/tick-snapshot.d.ts +28 -0
- package/lib/ui/tick-snapshot.js +34 -0
- package/lib/ui/ui-protocol.d.ts +799 -0
- package/lib/ui/ui-protocol.js +50 -0
- package/lib/ui/useAsset.d.ts +80 -0
- package/lib/ui/useAsset.js +139 -0
- package/lib/ui/useEndpointCallbacks.d.ts +31 -0
- package/lib/ui/useEndpointCallbacks.js +68 -0
- package/lib/ui/useHostChannel.d.ts +90 -0
- package/lib/ui/useHostChannel.js +338 -0
- package/lib/ui/useMutationReporter.d.ts +100 -0
- package/lib/ui/useMutationReporter.js +97 -0
- package/lib/ui/usePastState.d.ts +39 -0
- package/lib/ui/usePastState.js +63 -0
- package/lib/ui/useViewerStorage.d.ts +54 -0
- package/lib/ui/useViewerStorage.js +141 -0
- package/package.json +83 -3
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { CrossTeamDataContext } from './CrossTeamDataContext.js';
|
|
4
|
+
import { useDocumentAttributes } from './internal/document-attributes.js';
|
|
5
|
+
import { DEFAULT_SURFACE_TIMER } from './internal/surface-timer.js';
|
|
6
|
+
import { SurfaceGate } from './internal/SurfaceGate.js';
|
|
7
|
+
import { SurfaceScope } from './internal/SurfaceScope.js';
|
|
8
|
+
import { useLatestRef } from './internal/useLatestRef.js';
|
|
9
|
+
import { useOptimisticMerge } from './internal/useOptimisticMerge.js';
|
|
10
|
+
import { useTypedPastState } from './internal/useTypedPastState.js';
|
|
11
|
+
import { useHostChannel } from './useHostChannel.js';
|
|
12
|
+
import { UserInterfaceCallbacksContext } from './UserInterfaceCallbacksContext.js';
|
|
13
|
+
import { UserInterfaceDataContext } from './UserInterfaceDataContext.js';
|
|
14
|
+
import { UserInterfaceLocaleContext } from './UserInterfaceLocaleContext.js';
|
|
15
|
+
import { UserInterfaceOwnTeamContext } from './UserInterfaceOwnTeamContext.js';
|
|
16
|
+
import { UserInterfaceOwnTeamJoinLinkContext, } from './UserInterfaceOwnTeamJoinLinkContext.js';
|
|
17
|
+
import { UserInterfaceParticipantContext } from './UserInterfaceParticipantContext.js';
|
|
18
|
+
import { UserInterfaceParticipantsContext } from './UserInterfaceParticipantsContext.js';
|
|
19
|
+
import { UserInterfaceRunKindContext } from './UserInterfaceRunKindContext.js';
|
|
20
|
+
import { UserInterfaceTeamsContext } from './UserInterfaceTeamsContext.js';
|
|
21
|
+
import { UserInterfaceThemeContext } from './UserInterfaceThemeContext.js';
|
|
22
|
+
import { UserInterfaceThemePreferenceContext } from './UserInterfaceThemePreferenceContext.js';
|
|
23
|
+
import { UserInterfaceTimerContext } from './UserInterfaceTimerContext.js';
|
|
24
|
+
import { UserInterfaceValidationContext } from './UserInterfaceValidationContext.js';
|
|
25
|
+
import { UserInterfaceViewerRoleContext, } from './UserInterfaceViewerRoleContext.js';
|
|
26
|
+
const REQUIRED_MESSAGES = [
|
|
27
|
+
'stateChanged',
|
|
28
|
+
'setSettings',
|
|
29
|
+
'setTheme',
|
|
30
|
+
'setLocale',
|
|
31
|
+
'setParticipant',
|
|
32
|
+
'setAssetUrls',
|
|
33
|
+
];
|
|
34
|
+
function UserInterfaceWrapper({ children, fallback = _jsx("div", { children: "Loading..." }), targetWindow = window.parent, }) {
|
|
35
|
+
const [data, setDataState] = useState({});
|
|
36
|
+
const [completedTicks, setCompletedTicks] = useState(0);
|
|
37
|
+
// Snapshot of the most recent host-confirmed data. Used to roll back local
|
|
38
|
+
// state when a UI-submitted mutation is rejected by validators — without
|
|
39
|
+
// this, the iframe would keep its optimistic value while the host's
|
|
40
|
+
// persisted state diverges.
|
|
41
|
+
const lastHostDataRef = useRef({});
|
|
42
|
+
const [settings, setSettingsState] = useState(undefined);
|
|
43
|
+
const [crossTeamData, setCrossTeamDataState] = useState([]);
|
|
44
|
+
const [theme, setThemeState] = useState(undefined);
|
|
45
|
+
const [locale, setLocaleState] = useState(undefined);
|
|
46
|
+
// `null` is a real value here, not a placeholder: an observer has no caller
|
|
47
|
+
// identity. A participant render replaces it via setParticipant; an observer
|
|
48
|
+
// render leaves it null and satisfies the participant gate via setViewerRole.
|
|
49
|
+
const [participant, setParticipantState] = useState(null);
|
|
50
|
+
const [viewerRole, setViewerRoleState] = useState('participant');
|
|
51
|
+
// Fixed for the life of the run. 'session' is what a host offering no
|
|
52
|
+
// walkthroughs means, so it never gates first paint.
|
|
53
|
+
const [runKind, setRunKindState] = useState('session');
|
|
54
|
+
const [participants, setParticipantsState] = useState([]);
|
|
55
|
+
const [teams, setTeamsState] = useState([]);
|
|
56
|
+
// The team this surface renders, as the host reported it. Populated for
|
|
57
|
+
// observers too, which is the whole point: an observer has no participant to
|
|
58
|
+
// derive a team from (#332).
|
|
59
|
+
const [hostOwnTeam, setHostOwnTeamState] = useState(null);
|
|
60
|
+
// How to join this surface's own team. Never gates first paint: a join
|
|
61
|
+
// panel renders a placeholder rather than holding up the canvas.
|
|
62
|
+
const [ownTeamJoinLink, setOwnTeamJoinLinkState] = useState(null);
|
|
63
|
+
const [timer, setTimerState] = useState(DEFAULT_SURFACE_TIMER);
|
|
64
|
+
// Not processing is the honest default and the overwhelmingly common state,
|
|
65
|
+
// so this does not gate first paint: a surface that never hears about it
|
|
66
|
+
// renders as it always did.
|
|
67
|
+
const [isRoundProcessing, setRoundProcessingState] = useState(false);
|
|
68
|
+
const [validationRejections, setValidationRejections] = useState([]);
|
|
69
|
+
const channel = useHostChannel({
|
|
70
|
+
targetWindow,
|
|
71
|
+
label: 'UserInterfaceWrapper',
|
|
72
|
+
requiredMessages: REQUIRED_MESSAGES,
|
|
73
|
+
onMessage: (msg, { markReceived }) => {
|
|
74
|
+
if (msg.type === 'stateChanged') {
|
|
75
|
+
const payload = msg.payload ?? {};
|
|
76
|
+
setDataState(payload);
|
|
77
|
+
lastHostDataRef.current = payload;
|
|
78
|
+
if (typeof msg.completedTicks === 'number') {
|
|
79
|
+
setCompletedTicks(msg.completedTicks);
|
|
80
|
+
}
|
|
81
|
+
markReceived('stateChanged');
|
|
82
|
+
}
|
|
83
|
+
else if (msg.type === 'setCrossTeamData') {
|
|
84
|
+
setCrossTeamDataState(msg.payload ?? []);
|
|
85
|
+
}
|
|
86
|
+
else if (msg.type === 'setSettings') {
|
|
87
|
+
setSettingsState(msg.payload);
|
|
88
|
+
markReceived('setSettings');
|
|
89
|
+
}
|
|
90
|
+
else if (msg.type === 'setTheme') {
|
|
91
|
+
setThemeState(msg.payload);
|
|
92
|
+
markReceived('setTheme');
|
|
93
|
+
}
|
|
94
|
+
else if (msg.type === 'setLocale') {
|
|
95
|
+
setLocaleState(msg.payload);
|
|
96
|
+
markReceived('setLocale');
|
|
97
|
+
}
|
|
98
|
+
else if (msg.type === 'setParticipant') {
|
|
99
|
+
setParticipantState(msg.payload);
|
|
100
|
+
markReceived('setParticipant');
|
|
101
|
+
}
|
|
102
|
+
else if (msg.type === 'setViewerRole') {
|
|
103
|
+
setViewerRoleState(msg.role);
|
|
104
|
+
if (msg.role === 'observer') {
|
|
105
|
+
// An observer has no caller identity and never receives
|
|
106
|
+
// setParticipant, so satisfy the required-message gate here
|
|
107
|
+
// and keep the participant null.
|
|
108
|
+
setParticipantState(null);
|
|
109
|
+
markReceived('setParticipant');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (msg.type === 'setRunKind') {
|
|
113
|
+
setRunKindState(msg.runKind);
|
|
114
|
+
}
|
|
115
|
+
else if (msg.type === 'setParticipantList') {
|
|
116
|
+
setParticipantsState(msg.participants);
|
|
117
|
+
}
|
|
118
|
+
else if (msg.type === 'setTeamList') {
|
|
119
|
+
setTeamsState(msg.payload);
|
|
120
|
+
}
|
|
121
|
+
else if (msg.type === 'setOwnTeam') {
|
|
122
|
+
setHostOwnTeamState(msg.payload);
|
|
123
|
+
}
|
|
124
|
+
else if (msg.type === 'setOwnTeamJoinLink') {
|
|
125
|
+
setOwnTeamJoinLinkState(msg.payload);
|
|
126
|
+
}
|
|
127
|
+
else if (msg.type === 'setTimer') {
|
|
128
|
+
setTimerState(msg.payload);
|
|
129
|
+
}
|
|
130
|
+
else if (msg.type === 'setRoundProcessing') {
|
|
131
|
+
setRoundProcessingState(msg.processing);
|
|
132
|
+
}
|
|
133
|
+
else if (msg.type === 'validationRejected') {
|
|
134
|
+
setValidationRejections(msg.rejections);
|
|
135
|
+
// Roll local state back to the host's last confirmed value so the
|
|
136
|
+
// optimistic write done by setData is discarded.
|
|
137
|
+
setDataState(lastHostDataRef.current);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
const { isInitialized, expectedParentOrigin, sendMutation, sendMutationReportingCommit, uploadBlob, readAsset, getPastState: getPastStateRaw, getAllTeamsParticipants, getAssetUrl, assetUrls, setThemePreference, endpoints, themePreference, sessionKey, } = channel;
|
|
142
|
+
useDocumentAttributes(locale?.locale, theme?.mode);
|
|
143
|
+
const getPastState = useTypedPastState(getPastStateRaw);
|
|
144
|
+
// The tick the participant is looking at is held here and compared against
|
|
145
|
+
// the tick the host reports committing into, so the pair never leaves the
|
|
146
|
+
// surface. Read from a ref rather than closed over, or every tick would
|
|
147
|
+
// rebuild `setData` and re-render every control that depends on its
|
|
148
|
+
// identity.
|
|
149
|
+
const completedTicksRef = useLatestRef(completedTicks);
|
|
150
|
+
const setData = useOptimisticMerge(data, setDataState, useCallback((update, deletes) => {
|
|
151
|
+
const composedAtTick = completedTicksRef.current;
|
|
152
|
+
return sendMutationReportingCommit({
|
|
153
|
+
type: 'updateData',
|
|
154
|
+
payload: update,
|
|
155
|
+
...(deletes?.length ? { deletes } : {}),
|
|
156
|
+
}).then(({ committedAtTick }) => ({
|
|
157
|
+
committedAtTick,
|
|
158
|
+
composedAtTick,
|
|
159
|
+
// Both numbers are only ever in one place at once, so the
|
|
160
|
+
// comparison lives here rather than in every caller.
|
|
161
|
+
late: typeof committedAtTick === 'number' && committedAtTick > composedAtTick,
|
|
162
|
+
}));
|
|
163
|
+
}, [sendMutationReportingCommit, completedTicksRef]));
|
|
164
|
+
const requestTick = useCallback(() => sendMutation({ type: 'requestTick' }), [sendMutation]);
|
|
165
|
+
// No teamId: the host resolves the caller's team, so a surface can only
|
|
166
|
+
// rename its own. The result arrives back through setOwnTeam/setTeamList,
|
|
167
|
+
// which is what to render rather than the name that was sent.
|
|
168
|
+
const renameOwnTeam = useCallback((name) => sendMutation({ type: 'renameOwnTeam', name }), [sendMutation]);
|
|
169
|
+
const clearValidationRejections = useCallback(() => {
|
|
170
|
+
setValidationRejections([]);
|
|
171
|
+
}, []);
|
|
172
|
+
// Memoize each context value so consumers only re-render when their slice
|
|
173
|
+
// actually changes — otherwise every state update would invalidate every
|
|
174
|
+
// context's value object identity and cascade through the whole tree.
|
|
175
|
+
const dataContextValue = useMemo(() => ({ data, settings, completedTicks, isRoundProcessing, setData }), [data, settings, completedTicks, isRoundProcessing, setData]);
|
|
176
|
+
const validationContextValue = useMemo(() => ({
|
|
177
|
+
rejections: validationRejections,
|
|
178
|
+
clearRejections: clearValidationRejections,
|
|
179
|
+
}), [validationRejections, clearValidationRejections]);
|
|
180
|
+
const callbacksContextValue = useMemo(() => ({
|
|
181
|
+
requestTick,
|
|
182
|
+
renameOwnTeam,
|
|
183
|
+
uploadBlob,
|
|
184
|
+
getAssetUrl,
|
|
185
|
+
readAsset,
|
|
186
|
+
getPastState,
|
|
187
|
+
getAllTeamsParticipants,
|
|
188
|
+
setThemePreference,
|
|
189
|
+
endpoints,
|
|
190
|
+
}), [
|
|
191
|
+
requestTick,
|
|
192
|
+
renameOwnTeam,
|
|
193
|
+
uploadBlob,
|
|
194
|
+
getAssetUrl,
|
|
195
|
+
readAsset,
|
|
196
|
+
getPastState,
|
|
197
|
+
getAllTeamsParticipants,
|
|
198
|
+
setThemePreference,
|
|
199
|
+
endpoints,
|
|
200
|
+
]);
|
|
201
|
+
// Prefer what the host reported. The participant fallback keeps a host that
|
|
202
|
+
// predates `setOwnTeam` working for participants; only an observer on such a
|
|
203
|
+
// host is left without a team, which is the case that was already broken.
|
|
204
|
+
const ownTeam = useMemo(() => {
|
|
205
|
+
if (hostOwnTeam)
|
|
206
|
+
return hostOwnTeam;
|
|
207
|
+
if (!participant)
|
|
208
|
+
return null;
|
|
209
|
+
return teams.find((team) => team.teamId === participant.teamId) ?? null;
|
|
210
|
+
}, [hostOwnTeam, participant, teams]);
|
|
211
|
+
const participantId = participant?.id;
|
|
212
|
+
const storageScope = useMemo(() => ({
|
|
213
|
+
sessionKey,
|
|
214
|
+
surface: 'player',
|
|
215
|
+
viewer: participantId ?? viewerRole,
|
|
216
|
+
}), [sessionKey, participantId, viewerRole]);
|
|
217
|
+
return (_jsx(SurfaceGate, { expectedParentOrigin: expectedParentOrigin, ready: isInitialized && theme !== undefined && locale !== undefined, fallback: fallback, children: _jsx(SurfaceScope, { storage: storageScope, assetUrls: assetUrls, getAssetUrl: getAssetUrl, locale: locale?.locale, getPastState: getPastState, children: _jsx(UserInterfaceDataContext.Provider, { value: dataContextValue, children: _jsx(CrossTeamDataContext.Provider, { value: crossTeamData, children: _jsx(UserInterfaceThemeContext.Provider, { value: theme, children: _jsx(UserInterfaceThemePreferenceContext.Provider, { value: themePreference, children: _jsx(UserInterfaceLocaleContext.Provider, { value: locale, children: _jsx(UserInterfaceRunKindContext.Provider, { value: runKind, children: _jsx(UserInterfaceViewerRoleContext.Provider, { value: viewerRole, children: _jsx(UserInterfaceParticipantContext.Provider, { value: participant, children: _jsx(UserInterfaceParticipantsContext.Provider, { value: participants, children: _jsx(UserInterfaceTeamsContext.Provider, { value: teams, children: _jsx(UserInterfaceOwnTeamContext.Provider, { value: ownTeam, children: _jsx(UserInterfaceOwnTeamJoinLinkContext.Provider, { value: ownTeamJoinLink, children: _jsx(UserInterfaceTimerContext.Provider, { value: timer, children: _jsx(UserInterfaceCallbacksContext.Provider, { value: callbacksContextValue, children: _jsx(UserInterfaceValidationContext.Provider, { value: validationContextValue, children: children }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) }));
|
|
218
|
+
}
|
|
219
|
+
export default UserInterfaceWrapper;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the `expectedParentOrigin` query parameter from `window.location` and
|
|
3
|
+
* validates it as a bare origin string (`scheme://host[:port]`, no path/query/
|
|
4
|
+
* fragment). Returns the canonicalised origin if valid, `null` otherwise.
|
|
5
|
+
*
|
|
6
|
+
* The wrapper uses this both as the `targetOrigin` for its outbound `ready`
|
|
7
|
+
* postMessage and as the allowed `event.origin` for inbound `init`. Without it
|
|
8
|
+
* the wrapper refuses to operate (see each wrapper for the user-facing error).
|
|
9
|
+
*/
|
|
10
|
+
export declare function readExpectedParentOrigin(): string | null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the `expectedParentOrigin` query parameter from `window.location` and
|
|
3
|
+
* validates it as a bare origin string (`scheme://host[:port]`, no path/query/
|
|
4
|
+
* fragment). Returns the canonicalised origin if valid, `null` otherwise.
|
|
5
|
+
*
|
|
6
|
+
* The wrapper uses this both as the `targetOrigin` for its outbound `ready`
|
|
7
|
+
* postMessage and as the allowed `event.origin` for inbound `init`. Without it
|
|
8
|
+
* the wrapper refuses to operate (see each wrapper for the user-facing error).
|
|
9
|
+
*/
|
|
10
|
+
export function readExpectedParentOrigin() {
|
|
11
|
+
const raw = new URLSearchParams(window.location.search).get('expectedParentOrigin');
|
|
12
|
+
if (!raw)
|
|
13
|
+
return null;
|
|
14
|
+
let url;
|
|
15
|
+
try {
|
|
16
|
+
url = new URL(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
22
|
+
return null;
|
|
23
|
+
if (url.pathname !== '/' && url.pathname !== '')
|
|
24
|
+
return null;
|
|
25
|
+
if (url.search)
|
|
26
|
+
return null;
|
|
27
|
+
if (url.hash)
|
|
28
|
+
return null;
|
|
29
|
+
if (url.username || url.password)
|
|
30
|
+
return null;
|
|
31
|
+
return url.origin;
|
|
32
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export { default as UserInterfaceWrapper } from './UserInterfaceWrapper.js';
|
|
2
|
+
export { useUserInterfaceData, UserInterfaceDataContext } from './UserInterfaceDataContext.js';
|
|
3
|
+
export { useUserInterfaceTheme, type UserInterfaceTheme, type UserInterfaceModeColors, UserInterfaceThemeContext, } from './UserInterfaceThemeContext.js';
|
|
4
|
+
export { useUserInterfaceThemePreference, UserInterfaceThemePreferenceContext, } from './UserInterfaceThemePreferenceContext.js';
|
|
5
|
+
export { useUserInterfaceLocale, type UserInterfaceLocale, UserInterfaceLocaleContext, } from './UserInterfaceLocaleContext.js';
|
|
6
|
+
export { useUserInterfaceParticipant, UserInterfaceParticipantContext, } from './UserInterfaceParticipantContext.js';
|
|
7
|
+
export { useUserInterfaceViewerRole, type ViewerRole, UserInterfaceViewerRoleContext, } from './UserInterfaceViewerRoleContext.js';
|
|
8
|
+
export { useRunKind, type RunKind, UserInterfaceRunKindContext, } from './UserInterfaceRunKindContext.js';
|
|
9
|
+
export { useUserInterfaceTimer, UserInterfaceTimerContext } from './UserInterfaceTimerContext.js';
|
|
10
|
+
export { useViewerStorage, ViewerStorageContext, type ViewerStorageScope, type ViewerStorageSurface, } from './useViewerStorage.js';
|
|
11
|
+
export { useAssetUrl, useAsset, localisedReferences, AssetContext, type AssetFormat, type AssetScope, type AssetState, type UseAssetOptions, } from './useAsset.js';
|
|
12
|
+
export { usePastState, PastStateContext, type PastStateReader, type PastStateResult, } from './usePastState.js';
|
|
13
|
+
export type { ThemeColorRole, ThemeContrastText } from './internal/createContexts.js';
|
|
14
|
+
export type { TimerState } from '../build/timer.js';
|
|
15
|
+
export { useUserInterfaceParticipants, UserInterfaceParticipantsContext, } from './UserInterfaceParticipantsContext.js';
|
|
16
|
+
export { useUserInterfaceTeams, UserInterfaceTeamsContext } from './UserInterfaceTeamsContext.js';
|
|
17
|
+
export { useUserInterfaceOwnTeam, UserInterfaceOwnTeamContext, } from './UserInterfaceOwnTeamContext.js';
|
|
18
|
+
export { useUserInterfaceOwnTeamJoinLink, type OwnTeamJoinLink, UserInterfaceOwnTeamJoinLinkContext, } from './UserInterfaceOwnTeamJoinLinkContext.js';
|
|
19
|
+
export { useUserInterfaceCrossTeamData, CrossTeamDataContext, type CrossTeamData, } from './CrossTeamDataContext.js';
|
|
20
|
+
export { useUserInterfaceCallbacks, UserInterfaceCallbacksContext, } from './UserInterfaceCallbacksContext.js';
|
|
21
|
+
export { useUserInterfaceValidationRejections, type UserInterfaceValidationContextValue, UserInterfaceValidationContext, } from './UserInterfaceValidationContext.js';
|
|
22
|
+
export { default as SettingsEditorWrapper } from './SettingsEditorWrapper.js';
|
|
23
|
+
export { useSettingsEditor, type SettingsEditorValue, SettingsEditorContext, } from './SettingsEditorContext.js';
|
|
24
|
+
export { useSettingsEditorTheme, type SettingsEditorTheme, type SettingsEditorModeColors, SettingsEditorThemeContext, } from './SettingsEditorThemeContext.js';
|
|
25
|
+
export { useSettingsEditorThemePreference, SettingsEditorThemePreferenceContext, } from './SettingsEditorThemePreferenceContext.js';
|
|
26
|
+
export { useSettingsEditorLocale, type SettingsEditorLocale, SettingsEditorLocaleContext, } from './SettingsEditorLocaleContext.js';
|
|
27
|
+
export { useSettingsEditorCallbacks, SettingsEditorCallbacksContext, } from './SettingsEditorCallbacksContext.js';
|
|
28
|
+
export { default as SessionUserInterfaceWrapper } from './SessionUserInterfaceWrapper.js';
|
|
29
|
+
export { useSessionUserInterfaceData, SessionUserInterfaceDataContext, type SessionData, type SharedState, type TeamState, } from './SessionUserInterfaceDataContext.js';
|
|
30
|
+
export { useSessionUserInterfaceTheme, type SessionUserInterfaceTheme, type SessionUserInterfaceModeColors, SessionUserInterfaceThemeContext, } from './SessionUserInterfaceThemeContext.js';
|
|
31
|
+
export { useSessionUserInterfaceThemePreference, SessionUserInterfaceThemePreferenceContext, } from './SessionUserInterfaceThemePreferenceContext.js';
|
|
32
|
+
export { useSessionUserInterfaceLocale, type SessionUserInterfaceLocale, SessionUserInterfaceLocaleContext, } from './SessionUserInterfaceLocaleContext.js';
|
|
33
|
+
export { useSessionUserInterfaceCallbacks, SessionUserInterfaceCallbacksContext, } from './SessionUserInterfaceCallbacksContext.js';
|
|
34
|
+
export { useSessionUserInterfaceParticipants, SessionUserInterfaceParticipantsContext, } from './SessionUserInterfaceParticipantsContext.js';
|
|
35
|
+
export { useSessionUserInterfaceHookFailures, type SessionHookFailure, type SessionUserInterfaceHookFailuresValue, SessionUserInterfaceHookFailuresContext, } from './SessionUserInterfaceHookFailuresContext.js';
|
|
36
|
+
export { useSessionUserInterfaceHostOverlays, type HostOverlay, SessionUserInterfaceHostOverlaysContext, } from './SessionUserInterfaceHostOverlaysContext.js';
|
|
37
|
+
export { useSessionUserInterfaceMeta, type SessionMeta, SessionUserInterfaceMetaContext, } from './SessionUserInterfaceMetaContext.js';
|
|
38
|
+
export { useSessionUserInterfaceTeamLinks, type TeamLinks, SessionUserInterfaceTeamLinksContext, } from './SessionUserInterfaceTeamLinksContext.js';
|
|
39
|
+
export { useSessionUserInterfaceWalkthroughLink, type WalkthroughLink, SessionUserInterfaceWalkthroughLinkContext, } from './SessionUserInterfaceWalkthroughLinkContext.js';
|
|
40
|
+
export { useSessionUserInterfaceTeamConfiguration, type TeamConfiguration, SessionUserInterfaceTeamConfigurationContext, } from './SessionUserInterfaceTeamConfigurationContext.js';
|
|
41
|
+
export { useSessionUserInterfaceTeams, SessionUserInterfaceTeamsContext, } from './SessionUserInterfaceTeamsContext.js';
|
|
42
|
+
export { PresentationWrapper } from './PresentationWrapper.js';
|
|
43
|
+
export { usePresentation, PresentationContext, type PresentationGame, type PresentationSession, type PresentationValue, } from './PresentationContext.js';
|
|
44
|
+
export { MutationError, type MutationOutcome } from './mutation-outcome.js';
|
|
45
|
+
export { EndpointError, type EffectStep, type EndpointFailure, type EndpointFailureOutcome, } from '../build/endpoints.js';
|
|
46
|
+
export { useMutationReporter, type MutationFailure, type MutationReporter, } from './useMutationReporter.js';
|
|
47
|
+
export { TickSnapshotError } from './tick-snapshot.js';
|
|
48
|
+
export type { Participant, PastStateEntry, PastStateQuery, TeamInfo, TeamStateEntry, } from '../build/callbacks.js';
|
|
49
|
+
export type { ContextDeletePath, ContextUpdate } from '../build/context-merge.js';
|
|
50
|
+
export type { SetDataOptions } from './UserInterfaceDataContext.js';
|
package/lib/ui/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export { default as UserInterfaceWrapper } from './UserInterfaceWrapper.js';
|
|
2
|
+
export { useUserInterfaceData, UserInterfaceDataContext } from './UserInterfaceDataContext.js';
|
|
3
|
+
export { useUserInterfaceTheme, UserInterfaceThemeContext, } from './UserInterfaceThemeContext.js';
|
|
4
|
+
export { useUserInterfaceThemePreference, UserInterfaceThemePreferenceContext, } from './UserInterfaceThemePreferenceContext.js';
|
|
5
|
+
export { useUserInterfaceLocale, UserInterfaceLocaleContext, } from './UserInterfaceLocaleContext.js';
|
|
6
|
+
export { useUserInterfaceParticipant, UserInterfaceParticipantContext, } from './UserInterfaceParticipantContext.js';
|
|
7
|
+
export { useUserInterfaceViewerRole, UserInterfaceViewerRoleContext, } from './UserInterfaceViewerRoleContext.js';
|
|
8
|
+
export { useRunKind, UserInterfaceRunKindContext, } from './UserInterfaceRunKindContext.js';
|
|
9
|
+
export { useUserInterfaceTimer, UserInterfaceTimerContext } from './UserInterfaceTimerContext.js';
|
|
10
|
+
export { useViewerStorage, ViewerStorageContext, } from './useViewerStorage.js';
|
|
11
|
+
export { useAssetUrl, useAsset, localisedReferences, AssetContext, } from './useAsset.js';
|
|
12
|
+
export { usePastState, PastStateContext, } from './usePastState.js';
|
|
13
|
+
export { useUserInterfaceParticipants, UserInterfaceParticipantsContext, } from './UserInterfaceParticipantsContext.js';
|
|
14
|
+
export { useUserInterfaceTeams, UserInterfaceTeamsContext } from './UserInterfaceTeamsContext.js';
|
|
15
|
+
export { useUserInterfaceOwnTeam, UserInterfaceOwnTeamContext, } from './UserInterfaceOwnTeamContext.js';
|
|
16
|
+
export { useUserInterfaceOwnTeamJoinLink, UserInterfaceOwnTeamJoinLinkContext, } from './UserInterfaceOwnTeamJoinLinkContext.js';
|
|
17
|
+
export { useUserInterfaceCrossTeamData, CrossTeamDataContext, } from './CrossTeamDataContext.js';
|
|
18
|
+
export { useUserInterfaceCallbacks, UserInterfaceCallbacksContext, } from './UserInterfaceCallbacksContext.js';
|
|
19
|
+
export { useUserInterfaceValidationRejections, UserInterfaceValidationContext, } from './UserInterfaceValidationContext.js';
|
|
20
|
+
export { default as SettingsEditorWrapper } from './SettingsEditorWrapper.js';
|
|
21
|
+
export { useSettingsEditor, SettingsEditorContext, } from './SettingsEditorContext.js';
|
|
22
|
+
export { useSettingsEditorTheme, SettingsEditorThemeContext, } from './SettingsEditorThemeContext.js';
|
|
23
|
+
export { useSettingsEditorThemePreference, SettingsEditorThemePreferenceContext, } from './SettingsEditorThemePreferenceContext.js';
|
|
24
|
+
export { useSettingsEditorLocale, SettingsEditorLocaleContext, } from './SettingsEditorLocaleContext.js';
|
|
25
|
+
export { useSettingsEditorCallbacks, SettingsEditorCallbacksContext, } from './SettingsEditorCallbacksContext.js';
|
|
26
|
+
export { default as SessionUserInterfaceWrapper } from './SessionUserInterfaceWrapper.js';
|
|
27
|
+
export { useSessionUserInterfaceData, SessionUserInterfaceDataContext, } from './SessionUserInterfaceDataContext.js';
|
|
28
|
+
export { useSessionUserInterfaceTheme, SessionUserInterfaceThemeContext, } from './SessionUserInterfaceThemeContext.js';
|
|
29
|
+
export { useSessionUserInterfaceThemePreference, SessionUserInterfaceThemePreferenceContext, } from './SessionUserInterfaceThemePreferenceContext.js';
|
|
30
|
+
export { useSessionUserInterfaceLocale, SessionUserInterfaceLocaleContext, } from './SessionUserInterfaceLocaleContext.js';
|
|
31
|
+
export { useSessionUserInterfaceCallbacks, SessionUserInterfaceCallbacksContext, } from './SessionUserInterfaceCallbacksContext.js';
|
|
32
|
+
export { useSessionUserInterfaceParticipants, SessionUserInterfaceParticipantsContext, } from './SessionUserInterfaceParticipantsContext.js';
|
|
33
|
+
export { useSessionUserInterfaceHookFailures, SessionUserInterfaceHookFailuresContext, } from './SessionUserInterfaceHookFailuresContext.js';
|
|
34
|
+
export { useSessionUserInterfaceHostOverlays, SessionUserInterfaceHostOverlaysContext, } from './SessionUserInterfaceHostOverlaysContext.js';
|
|
35
|
+
export { useSessionUserInterfaceMeta, SessionUserInterfaceMetaContext, } from './SessionUserInterfaceMetaContext.js';
|
|
36
|
+
export { useSessionUserInterfaceTeamLinks, SessionUserInterfaceTeamLinksContext, } from './SessionUserInterfaceTeamLinksContext.js';
|
|
37
|
+
export { useSessionUserInterfaceWalkthroughLink, SessionUserInterfaceWalkthroughLinkContext, } from './SessionUserInterfaceWalkthroughLinkContext.js';
|
|
38
|
+
export { useSessionUserInterfaceTeamConfiguration, SessionUserInterfaceTeamConfigurationContext, } from './SessionUserInterfaceTeamConfigurationContext.js';
|
|
39
|
+
export { useSessionUserInterfaceTeams, SessionUserInterfaceTeamsContext, } from './SessionUserInterfaceTeamsContext.js';
|
|
40
|
+
// The wire protocol the wrappers speak (./ui-protocol.ts) is deliberately not
|
|
41
|
+
// exported: game code never handles a message. A host implementation takes the
|
|
42
|
+
// host's definition from @seriousgaming/sdk-tools/host.
|
|
43
|
+
export { PresentationWrapper } from './PresentationWrapper.js';
|
|
44
|
+
export { usePresentation, PresentationContext, } from './PresentationContext.js';
|
|
45
|
+
export { MutationError } from './mutation-outcome.js';
|
|
46
|
+
export { EndpointError, } from '../build/endpoints.js';
|
|
47
|
+
export { useMutationReporter, } from './useMutationReporter.js';
|
|
48
|
+
export { TickSnapshotError } from './tick-snapshot.js';
|
|
49
|
+
// The render/mock testing helpers are published from the dedicated
|
|
50
|
+
// `@seriousgaming/sdk/ui/testing` subpath (see ./testing.ts)
|
|
51
|
+
// so the main `/ui` entry does not pull in the optional @testing-library/react
|
|
52
|
+
// peer at runtime.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from 'react';
|
|
2
|
+
interface SurfaceGateProps {
|
|
3
|
+
/** The pinned parent origin; a missing/malformed value is a config error. */
|
|
4
|
+
expectedParentOrigin: string | null;
|
|
5
|
+
/** Whether the wrapper has received everything it needs to render children. */
|
|
6
|
+
ready: boolean;
|
|
7
|
+
/** Shown while `ready` is false. */
|
|
8
|
+
fallback: ReactNode;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The shared render gate every surface wrapper puts in front of its provider
|
|
13
|
+
* tree: an inline error when the asset-host origin is unconfigured, the
|
|
14
|
+
* fallback until the wrapper is `ready`, otherwise the children. Callers
|
|
15
|
+
* compute `ready` so surfaces with extra conditions (e.g. configuration's
|
|
16
|
+
* `surface`) can AND them in without a bespoke prop.
|
|
17
|
+
*/
|
|
18
|
+
export declare function SurfaceGate({ expectedParentOrigin, ready, fallback, children, }: SurfaceGateProps): ReactElement;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The shared render gate every surface wrapper puts in front of its provider
|
|
4
|
+
* tree: an inline error when the asset-host origin is unconfigured, the
|
|
5
|
+
* fallback until the wrapper is `ready`, otherwise the children. Callers
|
|
6
|
+
* compute `ready` so surfaces with extra conditions (e.g. configuration's
|
|
7
|
+
* `surface`) can AND them in without a bespoke prop.
|
|
8
|
+
*/
|
|
9
|
+
export function SurfaceGate({ expectedParentOrigin, ready, fallback, children, }) {
|
|
10
|
+
if (!expectedParentOrigin) {
|
|
11
|
+
return (_jsx("div", { role: "alert", children: "Asset host configuration error: expectedParentOrigin missing or malformed." }));
|
|
12
|
+
}
|
|
13
|
+
if (!ready) {
|
|
14
|
+
return _jsx(_Fragment, { children: fallback });
|
|
15
|
+
}
|
|
16
|
+
return _jsx(_Fragment, { children: children });
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { AssetUrls } from '../ui-protocol.js';
|
|
3
|
+
import { type PastStateReader } from '../usePastState.js';
|
|
4
|
+
import { type ViewerStorageScope } from '../useViewerStorage.js';
|
|
5
|
+
/**
|
|
6
|
+
* The contexts behind the hooks every surface shares (`useViewerStorage`,
|
|
7
|
+
* `useAssetUrl`, `useAsset` and, where the surface can read history,
|
|
8
|
+
* `usePastState`), provided once per wrapper.
|
|
9
|
+
*/
|
|
10
|
+
export declare function SurfaceScope({ storage, assetUrls, getAssetUrl, locale, getPastState, children, }: {
|
|
11
|
+
storage: ViewerStorageScope;
|
|
12
|
+
assetUrls: AssetUrls | undefined;
|
|
13
|
+
getAssetUrl: (reference: string) => string;
|
|
14
|
+
locale: string | undefined;
|
|
15
|
+
getPastState?: PastStateReader;
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { AssetContext } from '../useAsset.js';
|
|
4
|
+
import { PastStateContext } from '../usePastState.js';
|
|
5
|
+
import { ViewerStorageContext } from '../useViewerStorage.js';
|
|
6
|
+
/**
|
|
7
|
+
* The contexts behind the hooks every surface shares (`useViewerStorage`,
|
|
8
|
+
* `useAssetUrl`, `useAsset` and, where the surface can read history,
|
|
9
|
+
* `usePastState`), provided once per wrapper.
|
|
10
|
+
*/
|
|
11
|
+
export function SurfaceScope({ storage, assetUrls, getAssetUrl, locale, getPastState, children, }) {
|
|
12
|
+
const ready = assetUrls !== undefined;
|
|
13
|
+
const assets = useMemo(() => ({ getAssetUrl: ready ? getAssetUrl : undefined, locale }), [ready, getAssetUrl, locale]);
|
|
14
|
+
const scoped = (_jsx(ViewerStorageContext.Provider, { value: storage, children: _jsx(AssetContext.Provider, { value: assets, children: children }) }));
|
|
15
|
+
return getPastState ? (_jsx(PastStateContext.Provider, { value: getPastState, children: scoped })) : (scoped);
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type Context } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Colour roles shared by every surface's theme. Internal only — each surface
|
|
4
|
+
* re-declares its public `*Theme` type locally (referencing this) so the
|
|
5
|
+
* published type names are unchanged.
|
|
6
|
+
*/
|
|
7
|
+
export type SurfaceModeColors = {
|
|
8
|
+
primary: string;
|
|
9
|
+
secondary: string;
|
|
10
|
+
success: string;
|
|
11
|
+
error: string;
|
|
12
|
+
warning: string;
|
|
13
|
+
info: string;
|
|
14
|
+
/**
|
|
15
|
+
* The text colour to draw on each of the colours above, as an opaque
|
|
16
|
+
* `#rrggbb`, meeting WCAG AA (4.5:1) against it (§12.3).
|
|
17
|
+
*/
|
|
18
|
+
contrastText: ThemeContrastText;
|
|
19
|
+
};
|
|
20
|
+
/** The palette roles a theme sends, each a colour a surface may paint with. */
|
|
21
|
+
export type ThemeColorRole = 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info';
|
|
22
|
+
/** One text colour per palette role, for text drawn on that role's colour. */
|
|
23
|
+
export type ThemeContrastText = Record<ThemeColorRole, string>;
|
|
24
|
+
export interface RequiredContext<T> {
|
|
25
|
+
Context: Context<T | undefined>;
|
|
26
|
+
/** Read the value; throws with a surface-specific message if unprovided. */
|
|
27
|
+
useValue: () => T;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build a context whose consumer hook throws when read outside its wrapper.
|
|
31
|
+
* Backs the boilerplate "throwing" context roles (theme, theme-preference,
|
|
32
|
+
* callbacks, locale, surface, ui-list, validation, participant(s)); each
|
|
33
|
+
* surface file wraps this and re-exports the concrete `*Context` / `use*`
|
|
34
|
+
* names so the published API is unchanged.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createRequiredContext<T>(options: {
|
|
37
|
+
hookName: string;
|
|
38
|
+
wrapperName: string;
|
|
39
|
+
}): RequiredContext<T>;
|
|
40
|
+
export interface DefaultedContext<T> {
|
|
41
|
+
Context: Context<T>;
|
|
42
|
+
/** Read the value; always defined (falls back to the default). */
|
|
43
|
+
useValue: () => T;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build a context that always returns a value, defaulting to `defaultValue`
|
|
47
|
+
* when unprovided. Backs the non-throwing roles (timer, basic team list,
|
|
48
|
+
* player participant list).
|
|
49
|
+
*/
|
|
50
|
+
export declare function createDefaultedContext<T>(defaultValue: T): DefaultedContext<T>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Build a context whose consumer hook throws when read outside its wrapper.
|
|
4
|
+
* Backs the boilerplate "throwing" context roles (theme, theme-preference,
|
|
5
|
+
* callbacks, locale, surface, ui-list, validation, participant(s)); each
|
|
6
|
+
* surface file wraps this and re-exports the concrete `*Context` / `use*`
|
|
7
|
+
* names so the published API is unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export function createRequiredContext(options) {
|
|
10
|
+
const { hookName, wrapperName } = options;
|
|
11
|
+
const ContextObject = createContext(undefined);
|
|
12
|
+
function useValue() {
|
|
13
|
+
const value = useContext(ContextObject);
|
|
14
|
+
if (value === undefined) {
|
|
15
|
+
throw new Error(`${hookName} must be used inside a ${wrapperName}`);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
return { Context: ContextObject, useValue };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build a context that always returns a value, defaulting to `defaultValue`
|
|
23
|
+
* when unprovided. Backs the non-throwing roles (timer, basic team list,
|
|
24
|
+
* player participant list).
|
|
25
|
+
*/
|
|
26
|
+
export function createDefaultedContext(defaultValue) {
|
|
27
|
+
const ContextObject = createContext(defaultValue);
|
|
28
|
+
function useValue() {
|
|
29
|
+
return useContext(ContextObject);
|
|
30
|
+
}
|
|
31
|
+
return { Context: ContextObject, useValue };
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The direction a locale's text runs in. */
|
|
2
|
+
export declare function textDirection(locale: string): 'ltr' | 'rtl';
|
|
3
|
+
/**
|
|
4
|
+
* Keep `lang`, `dir` and `color-scheme` on the document root in step with the
|
|
5
|
+
* surface's locale and theme, so a surface gets right-to-left layout, the
|
|
6
|
+
* correct hyphenation and voice, and dark scrollbars and form controls without
|
|
7
|
+
* setting any of them itself. Each is restored when the wrapper unmounts.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useDocumentAttributes(locale: string | undefined, mode: 'light' | 'dark' | undefined): void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Languages written right to left, for a runtime whose `Intl.Locale` cannot
|
|
4
|
+
* say. The script decides first where the tag names one, so `ku-Arab` is
|
|
5
|
+
* right to left and `ku-Latn` is not.
|
|
6
|
+
*/
|
|
7
|
+
const RTL_LANGUAGES = new Set([
|
|
8
|
+
'ar',
|
|
9
|
+
'arc',
|
|
10
|
+
'ckb',
|
|
11
|
+
'dv',
|
|
12
|
+
'fa',
|
|
13
|
+
'he',
|
|
14
|
+
'iw',
|
|
15
|
+
'ks',
|
|
16
|
+
'ps',
|
|
17
|
+
'sd',
|
|
18
|
+
'ug',
|
|
19
|
+
'ur',
|
|
20
|
+
'yi',
|
|
21
|
+
]);
|
|
22
|
+
const RTL_SCRIPTS = new Set(['Adlm', 'Arab', 'Hebr', 'Nkoo', 'Rohg', 'Syrc', 'Thaa']);
|
|
23
|
+
function fromIntl(locale) {
|
|
24
|
+
try {
|
|
25
|
+
const parsed = new Intl.Locale(locale);
|
|
26
|
+
const info = typeof parsed.getTextInfo === 'function' ? parsed.getTextInfo() : parsed.textInfo;
|
|
27
|
+
if (info?.direction === 'rtl' || info?.direction === 'ltr')
|
|
28
|
+
return info.direction;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Not a well-formed tag: the list below still has a view.
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
function fromList(locale) {
|
|
36
|
+
const parts = locale.split(/[-_]/);
|
|
37
|
+
const script = parts.find((part, index) => index > 0 && /^[A-Za-z]{4}$/.test(part));
|
|
38
|
+
if (script) {
|
|
39
|
+
const normalised = script[0].toUpperCase() + script.slice(1).toLowerCase();
|
|
40
|
+
return RTL_SCRIPTS.has(normalised) ? 'rtl' : 'ltr';
|
|
41
|
+
}
|
|
42
|
+
return RTL_LANGUAGES.has(parts[0].toLowerCase()) ? 'rtl' : 'ltr';
|
|
43
|
+
}
|
|
44
|
+
/** The direction a locale's text runs in. */
|
|
45
|
+
export function textDirection(locale) {
|
|
46
|
+
return fromIntl(locale) ?? fromList(locale);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Keep `lang`, `dir` and `color-scheme` on the document root in step with the
|
|
50
|
+
* surface's locale and theme, so a surface gets right-to-left layout, the
|
|
51
|
+
* correct hyphenation and voice, and dark scrollbars and form controls without
|
|
52
|
+
* setting any of them itself. Each is restored when the wrapper unmounts.
|
|
53
|
+
*/
|
|
54
|
+
export function useDocumentAttributes(locale, mode) {
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (locale === undefined || typeof document === 'undefined')
|
|
57
|
+
return;
|
|
58
|
+
const root = document.documentElement;
|
|
59
|
+
const previousLang = root.getAttribute('lang');
|
|
60
|
+
const previousDir = root.getAttribute('dir');
|
|
61
|
+
root.setAttribute('lang', locale);
|
|
62
|
+
root.setAttribute('dir', textDirection(locale));
|
|
63
|
+
return () => {
|
|
64
|
+
restore(root, 'lang', previousLang);
|
|
65
|
+
restore(root, 'dir', previousDir);
|
|
66
|
+
};
|
|
67
|
+
}, [locale]);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (mode === undefined || typeof document === 'undefined')
|
|
70
|
+
return;
|
|
71
|
+
const style = document.documentElement.style;
|
|
72
|
+
const previous = style.getPropertyValue('color-scheme');
|
|
73
|
+
style.setProperty('color-scheme', mode);
|
|
74
|
+
return () => {
|
|
75
|
+
if (previous)
|
|
76
|
+
style.setProperty('color-scheme', previous);
|
|
77
|
+
else
|
|
78
|
+
style.removeProperty('color-scheme');
|
|
79
|
+
};
|
|
80
|
+
}, [mode]);
|
|
81
|
+
}
|
|
82
|
+
function restore(root, name, previous) {
|
|
83
|
+
if (previous === null)
|
|
84
|
+
root.removeAttribute(name);
|
|
85
|
+
else
|
|
86
|
+
root.setAttribute(name, previous);
|
|
87
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The timer a surface holds before the host sends one: nothing scheduled. */
|
|
2
|
+
export const DEFAULT_SURFACE_TIMER = {
|
|
3
|
+
enabled: false,
|
|
4
|
+
paused: false,
|
|
5
|
+
nextTickAt: null,
|
|
6
|
+
intervalMs: null,
|
|
7
|
+
pausedAt: null,
|
|
8
|
+
pausedMs: 0,
|
|
9
|
+
extendedMs: 0,
|
|
10
|
+
scheduleStartedAt: null,
|
|
11
|
+
};
|