@tastic/shell 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -15
- package/dist/index.d.mts +29 -14
- package/dist/index.d.ts +29 -14
- package/dist/index.js +208 -182
- package/dist/index.mjs +139 -116
- package/package.json +16 -18
- package/src/RootLayout.tsx +27 -66
- package/src/components/GuideHost.tsx +4 -6
- package/src/components/MatchChips.tsx +72 -0
- package/src/components/SettingsDialog.tsx +4 -10
- package/src/components/Theme.tsx +3 -2
- package/src/components/UpdateDialog.tsx +1 -3
- package/src/hooks/useFrozenWhileHidden.ts +2 -2
- package/src/index.ts +1 -0
- package/src/redux/appSettingsSlice.ts +0 -11
- package/src/screens/HomeScreen.tsx +13 -17
- package/src/screens/LoadoutScreen.tsx +35 -53
- package/src/screens/ProfilesRoute.tsx +5 -5
- package/src/screens/StatsScreen.tsx +4 -5
- package/src/screens/homeLayout.ts +15 -19
- package/src/types.ts +13 -21
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { defaultColors, getRgb, useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
|
-
import {
|
|
2
|
+
import { useOrientationState } from '@tastic/core'
|
|
3
3
|
import { CornerActionButtons, PressAwayOverlay, usePopoverHost } from '@tastic/hud/chrome'
|
|
4
4
|
import { PlayerSetupPanel, ReadyButton, SharedActionBand } from '@tastic/hud/lobby'
|
|
5
5
|
import type { Profile } from '@tastic/profile'
|
|
6
6
|
import { DualZoneLayout, needsSharedNeutralZone, useDualZoneLayout } from '@tastic/split-screen'
|
|
7
7
|
import { router, useFocusEffect } from 'expo-router'
|
|
8
8
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
9
|
-
import {
|
|
9
|
+
import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native'
|
|
10
10
|
import Animated from 'react-native-reanimated'
|
|
11
11
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
12
12
|
import { useDispatch } from 'react-redux'
|
|
@@ -24,11 +24,11 @@ import { liveplayActions } from '../redux/liveplaySlice'
|
|
|
24
24
|
import { playHref, PROFILES_HREF } from '../routes'
|
|
25
25
|
import { humanSeatsFor, type PackGameDefinition, roundModeFor, type Seat, type SeatTwo, seatTwoFor } from '../types'
|
|
26
26
|
|
|
27
|
-
// The player-panel area: whenever
|
|
28
|
-
//
|
|
29
|
-
// back in - masking what would otherwise be an
|
|
30
|
-
// Short and symmetric on purpose: this is masking a reflow that
|
|
31
|
-
// pace, not a deliberate reveal.
|
|
27
|
+
// The player-panel area: whenever the window changes shape (a tablet turning, a browser window
|
|
28
|
+
// resized) and the panels go from face to face to side by side or back, they fade out, the layout
|
|
29
|
+
// underneath swaps while invisible, then they fade back in - masking what would otherwise be an
|
|
30
|
+
// instant jump-cut as panels reflow. Short and symmetric on purpose: this is masking a reflow that
|
|
31
|
+
// already happened at the OS's own pace, not a deliberate reveal.
|
|
32
32
|
const PANEL_SWAP_FADE_MS = 180
|
|
33
33
|
|
|
34
34
|
// A game that has a loadout (GameDefinition.useLoadout): the loadout route sends any other straight
|
|
@@ -53,32 +53,24 @@ export function LoadoutScreen({ game }: { game: LoadoutGame }) {
|
|
|
53
53
|
const { openFor } = useGameGuide()
|
|
54
54
|
|
|
55
55
|
const { profiles, lastSelected, selectProfile } = useProfiles()
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
// the shared reading itself (RootLayout's OrientationBridge), so this read takes no `locked`
|
|
60
|
-
// argument. The OS is portrait-locked; FakeLandscapeView below fakes the rest in JS.
|
|
61
|
-
const { orientationMode, p1OnRight, upsideDown, resolved: p1OnRightResolved } = useOrientationState()
|
|
56
|
+
// The window's shape (@tastic/core's useOrientationState): side by side in a window wider than tall,
|
|
57
|
+
// face to face otherwise. The OS turns the interface; a portrait-locked phone is always face to face.
|
|
58
|
+
const { orientationMode } = useOrientationState()
|
|
62
59
|
|
|
63
|
-
// The panel area's own layout lags one fade behind the
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const { panelLayout, panelFadeStyle } = useDualZoneLayout(fixedSeats ? 'faceToFace' : orientationMode, fixedSeats ? true : p1OnRight, fixedSeats || p1OnRightResolved, fixedSeats ? fixedUpsideDown : upsideDown, PANEL_SWAP_FADE_MS)
|
|
60
|
+
// The panel area's own layout lags one fade behind the window above - see useDualZoneLayout's own
|
|
61
|
+
// comment. 2 Player seats its players the way the game's own board does: face to face (P1 in the
|
|
62
|
+
// bottom half, P2 in the top) in any window, unless the game seats them side by side in a wide one
|
|
63
|
+
// (GameDefinition.sideBySideWhenWide), P1 on the left, so each player gets the same half here and
|
|
64
|
+
// in the match. With one person the panels follow the window.
|
|
65
|
+
const panelMode = p2IsHuman && !game.sideBySideWhenWide ? 'faceToFace' : orientationMode
|
|
66
|
+
const { panelLayout, panelFadeStyle } = useDualZoneLayout(panelMode, false, true, false, PANEL_SWAP_FADE_MS)
|
|
71
67
|
// split-screen types the fade as a style for any component (useAnimatedStyle's DefaultStyle). An app
|
|
72
68
|
// whose types widen Text's style (Expo's react-native-web types, which a generated expo-env.d.ts pulls
|
|
73
69
|
// in, make its cursor any string) can't hand that to Animated.View. It only ever sets opacity.
|
|
74
70
|
const playersRowFade = panelFadeStyle as unknown as StyleProp<ViewStyle>
|
|
75
71
|
|
|
76
72
|
const [settingsOpen, setSettingsOpen] = useState(false)
|
|
77
|
-
|
|
78
|
-
// onto whichever edge they land on once FakeLandscapeView turns the content, so `top`/`left`/
|
|
79
|
-
// `right` below always mean the screen's real, visual edges.
|
|
80
|
-
const rotation = getViewRotation(panelLayout.orientationMode, panelLayout.p1OnRight, panelLayout.upsideDown)
|
|
81
|
-
const insets = rotateInsets(useSafeAreaInsets(), rotation)
|
|
73
|
+
const insets = useSafeAreaInsets()
|
|
82
74
|
const { colors: themeColors, dark } = useAutoPaperTheme()
|
|
83
75
|
const { p1Color, p2Color, handleP1ColorChange, handleP2ColorChange } = useSeatColors(p2IsHuman)
|
|
84
76
|
const dispatch = useDispatch<Dispatch<Action>>()
|
|
@@ -142,9 +134,9 @@ export function LoadoutScreen({ game }: { game: LoadoutGame }) {
|
|
|
142
134
|
const showMetaInSharedBand = needsSharedNeutralZone(panelLayout.orientationMode, humanSeats.length)
|
|
143
135
|
|
|
144
136
|
// Where P2's own press-away zone lives on screen - matches panelLayout (what's actually painted
|
|
145
|
-
// right now, mid-fade included). Portrait puts P2's panel in the top half; landscape on
|
|
146
|
-
//
|
|
147
|
-
const p2ZoneStyle = p2IsHuman ? (isPortrait ? styles.p2ZoneTop :
|
|
137
|
+
// right now, mid-fade included). Portrait puts P2's panel in the top half; landscape on the right
|
|
138
|
+
// (P1 sits on the left). Only in 2 Player - otherwise seat 2 shares seat 1's host (see p2Host below).
|
|
139
|
+
const p2ZoneStyle = p2IsHuman ? (isPortrait ? styles.p2ZoneTop : styles.p2ZoneRight) : null
|
|
148
140
|
|
|
149
141
|
// With one human, the one Ready button sits under both panels instead of in each.
|
|
150
142
|
const showReadyButton = p2IsHuman
|
|
@@ -167,7 +159,6 @@ export function LoadoutScreen({ game }: { game: LoadoutGame }) {
|
|
|
167
159
|
|
|
168
160
|
const optionsRow = loadout.options({
|
|
169
161
|
host: controlsHost,
|
|
170
|
-
rotation,
|
|
171
162
|
accentColor: themeColors.tertiary,
|
|
172
163
|
mutedColor: fgMuted,
|
|
173
164
|
onAccentColor: themeColors.onTertiary,
|
|
@@ -186,7 +177,7 @@ export function LoadoutScreen({ game }: { game: LoadoutGame }) {
|
|
|
186
177
|
optionsRow
|
|
187
178
|
)
|
|
188
179
|
|
|
189
|
-
const controlLook = { accentColor: themeColors.tertiary, mutedColor: fgMuted, dark
|
|
180
|
+
const controlLook = { accentColor: themeColors.tertiary, mutedColor: fgMuted, dark }
|
|
190
181
|
const p1Control = loadout.seatControl(1, controlsHost, { ...controlLook, seatColor: p1Drawn })
|
|
191
182
|
const p1Panel = <PlayerSetupPanel key={`p1-${layoutKey}`} idPrefix='p1' host={controlsHost} color={p1Color} onColorChange={handleP1ColorChange} swatches={defaultColors} takenColor={p2Exists ? p2Color : undefined} allowSwapTaken={mode === 'vsCpu'} colorCaption={pack.colorCaption} isHuman ready={ready[1]} onToggleReady={() => setReady((r) => ({ ...r, 1: !r[1] }))} dark={dark} showReadyButton={showReadyButton} secondPicker={p1Control} secondPickerId={p1Control ? 'p1-control' : undefined} profiles={profiles} selectedProfileId={lastSelected[1]} takenProfileId={p2IsHuman ? lastSelected[2] : null} guestLabel={pack.guestLabel ?? 'P1'} nullLabel={pack.nullLabel} onProfileSelect={handleP1ProfileSelect} onManageProfiles={() => router.push(PROFILES_HREF)} previewValue={seatPreview} triggerStyle={triggerStyle} useSeatPickerAlign={seatPickerAlign} />
|
|
192
183
|
const p2Control = p2IsHuman ? loadout.seatControl(2, p2Host, { ...controlLook, seatColor: p2Drawn }) : undefined
|
|
@@ -195,10 +186,8 @@ export function LoadoutScreen({ game }: { game: LoadoutGame }) {
|
|
|
195
186
|
|
|
196
187
|
return (
|
|
197
188
|
<View style={[styles.container, { backgroundColor: bg }]}>
|
|
198
|
-
{/*
|
|
199
|
-
|
|
200
|
-
dialog stays outside: it renders through a Portal, turned by its own `rotation`. */}
|
|
201
|
-
<FakeLandscapeView orientationMode={panelLayout.orientationMode} p1OnRight={panelLayout.p1OnRight} upsideDown={panelLayout.upsideDown} style={styles.rotatable}>
|
|
189
|
+
{/* The Settings dialog stays outside this frame: it renders through a Portal. */}
|
|
190
|
+
<View style={styles.frame}>
|
|
202
191
|
{/* Press-away overlays for the loadout's own menus. P1's covers the whole screen (their
|
|
203
192
|
panel plus the match options read as theirs), while P2's covers only their half and, as a
|
|
204
193
|
later sibling, takes priority there. P2's mounts whenever either host has a menu open, so it
|
|
@@ -225,15 +214,15 @@ export function LoadoutScreen({ game }: { game: LoadoutGame }) {
|
|
|
225
214
|
<View style={styles.stackedZone}>
|
|
226
215
|
{sharedControls}
|
|
227
216
|
<Animated.View style={[styles.playersRow, anyPlayerPickerOpen && styles.playersRowOpen, playersRowFade]}>
|
|
228
|
-
{
|
|
229
|
-
{
|
|
217
|
+
{p1Panel}
|
|
218
|
+
{p2Panel}
|
|
230
219
|
</Animated.View>
|
|
231
220
|
<ReadyButton color={p1Drawn} ready={ready[1]} onToggleReady={() => setReady((r) => ({ ...r, 1: !r[1] }))} style={anyPlayerPickerOpen && styles.readyButtonHidden} />
|
|
232
221
|
</View>
|
|
233
222
|
)}
|
|
234
|
-
</
|
|
223
|
+
</View>
|
|
235
224
|
|
|
236
|
-
<SettingsDialog visible={settingsOpen} onDismiss={() => setSettingsOpen(false)}
|
|
225
|
+
<SettingsDialog visible={settingsOpen} onDismiss={() => setSettingsOpen(false)} onShowHowToPlay={() => openFor(game.id)} />
|
|
237
226
|
</View>
|
|
238
227
|
)
|
|
239
228
|
}
|
|
@@ -249,12 +238,13 @@ const styles = StyleSheet.create({
|
|
|
249
238
|
container: {
|
|
250
239
|
flex: 1
|
|
251
240
|
},
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
241
|
+
// A real full-bleed box to size the absolutely-positioned children against.
|
|
242
|
+
frame: {
|
|
243
|
+
alignItems: 'center',
|
|
244
|
+
flex: 1,
|
|
245
|
+
gap: 32,
|
|
246
|
+
justifyContent: 'center',
|
|
247
|
+
paddingHorizontal: 16
|
|
258
248
|
},
|
|
259
249
|
p2ZoneRight: {
|
|
260
250
|
bottom: 0,
|
|
@@ -289,14 +279,6 @@ const styles = StyleSheet.create({
|
|
|
289
279
|
opacity: 0,
|
|
290
280
|
pointerEvents: 'none'
|
|
291
281
|
},
|
|
292
|
-
// A real full-bleed box for FakeLandscapeView to size its absolutely-positioned children against.
|
|
293
|
-
rotatable: {
|
|
294
|
-
alignItems: 'center',
|
|
295
|
-
flex: 1,
|
|
296
|
-
gap: 32,
|
|
297
|
-
justifyContent: 'center',
|
|
298
|
-
paddingHorizontal: 16
|
|
299
|
-
},
|
|
300
282
|
stackedZone: {
|
|
301
283
|
alignItems: 'center',
|
|
302
284
|
gap: 28
|
|
@@ -6,7 +6,7 @@ import { useProfiles } from '../hooks/useProfiles'
|
|
|
6
6
|
import { safeBack } from '../navigation'
|
|
7
7
|
import { usePack } from '../PackContext'
|
|
8
8
|
|
|
9
|
-
// Widest the roster gets in the wide layout (a phone
|
|
9
|
+
// Widest the roster gets in the wide layout (a landscape phone, a desktop window). Upright it's
|
|
10
10
|
// never capped.
|
|
11
11
|
const ROSTER_MAX_WIDTH = 640
|
|
12
12
|
|
|
@@ -14,10 +14,10 @@ const ROSTER_MAX_WIDTH = 640
|
|
|
14
14
|
// stats and achievements in every game: each game's reducers listen for the removal (see
|
|
15
15
|
// onProfileRemoved.ts).
|
|
16
16
|
//
|
|
17
|
-
//
|
|
18
|
-
// safe-area insets itself (the header's padding),
|
|
19
|
-
//
|
|
20
|
-
//
|
|
17
|
+
// In hud's RotatedFrame, which nothing turns since @tastic/core 0.15: ProfilesManager reads the
|
|
18
|
+
// safe-area insets itself (the header's padding), handed over by rotatedSafeArea; its delete confirm is a
|
|
19
|
+
// Paper Portal, which lands in a host inside the frame (portalHost); and the roster's column is capped in
|
|
20
|
+
// the wide layout. Over a
|
|
21
21
|
// pack's Backdrop the screen is transparent, drawn in the pack's foreground.
|
|
22
22
|
// The pack's own placement for a profile's color swatch (PackDefinition.usePickerAlign), kept inside the
|
|
23
23
|
// roster's capped column: the roster scrolls in it, which clips the menu at its edges. Handed to
|
|
@@ -34,7 +34,7 @@ const ALL_GAMES = '__all__'
|
|
|
34
34
|
const PLAYER_PICKER_ID = 'stats-player'
|
|
35
35
|
const GAME_PICKER_ID = 'stats-game'
|
|
36
36
|
|
|
37
|
-
// Widest the column gets in the wide layout (a phone
|
|
37
|
+
// Widest the column gets in the wide layout (a landscape phone, a desktop window): a list of stat
|
|
38
38
|
// rows stretched across an 874pt-wide screen reads badly. Upright it's never capped.
|
|
39
39
|
const STATS_MAX_WIDTH = 640
|
|
40
40
|
|
|
@@ -45,9 +45,8 @@ const STATS_MAX_WIDTH = 640
|
|
|
45
45
|
// can't be told apart. A pack for one player at a time (PackDefinition.guestStats) keeps a guest's
|
|
46
46
|
// stats under GUEST_PLAYER_ID, offers Guest in the picker and opens on whoever is in seat 1.
|
|
47
47
|
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// capped in the wide layout.
|
|
48
|
+
// In hud's RotatedFrame, which nothing turns since @tastic/core 0.15: it hands the screen the safe-area
|
|
49
|
+
// insets, hosts the reset confirm's portal inside the frame, and caps the column in the wide layout.
|
|
51
50
|
export function StatsScreen() {
|
|
52
51
|
const pickerHost = usePopoverHost()
|
|
53
52
|
return (
|
|
@@ -81,7 +80,7 @@ const noPickerAlign = () => undefined
|
|
|
81
80
|
function StatsContent({ pickerHost }: { pickerHost: PopoverHost }) {
|
|
82
81
|
const pack = usePack()
|
|
83
82
|
const { dark, colors } = useAutoPaperTheme()
|
|
84
|
-
//
|
|
83
|
+
// Handed over by RotatedFrame's rotatedSafeArea.
|
|
85
84
|
const insets = useSafeAreaInsets()
|
|
86
85
|
const { profiles, lastSelected } = useProfiles()
|
|
87
86
|
const dispatch = useDispatch()
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import { clamp
|
|
1
|
+
import { clamp } from '@tastic/core'
|
|
2
|
+
import { useWindowDimensions } from 'react-native'
|
|
2
3
|
import { type EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
3
4
|
|
|
4
5
|
// Home's geometry for a pack whose games have previews (GameDefinition.Preview), ported from
|
|
5
6
|
// Solitaire's components/homeLayout.ts. Upright, Home is the one scrolling column every pack has: the
|
|
6
|
-
// hero, seat 1's button, then a row per game, each row's preview at its start.
|
|
7
|
+
// hero, seat 1's button, then a row per game, each row's preview at its start. A landscape phone
|
|
7
8
|
// is only ~400pt tall, and that column would spend the whole height on the hero before showing a
|
|
8
|
-
// single game, so a "wide" layout (any window
|
|
9
|
-
//
|
|
9
|
+
// single game, so a "wide" layout (any window wider than tall: a landscape phone, a landscape iPad, a
|
|
10
|
+
// desktop browser) splits it into two panes: the hero and seat 1's button on the
|
|
10
11
|
// left, and a scrolling grid of game tiles on the right. The trophy (top-left) and cog (top-right)
|
|
11
12
|
// corner buttons stay where they are; the right pane stops short of the cog so tiles never scroll
|
|
12
13
|
// underneath it. A pack without previews keeps the column at every size.
|
|
13
14
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// frame's edges (rotateInsets). The rotated top/bottom insets are ~0 under a portrait-locked OS (a
|
|
17
|
-
// phone held sideways only has left/right insets), so nothing here leans on them for breathing room.
|
|
15
|
+
// A landscape phone's top and bottom insets are ~0 (only the sides have them), so nothing here leans
|
|
16
|
+
// on them for breathing room.
|
|
18
17
|
|
|
19
18
|
// Every preview's width / height: Solitaire's preview canvas (360 x 200), so its deals fit as drawn.
|
|
20
19
|
export const PREVIEW_ASPECT = 360 / 200
|
|
@@ -52,15 +51,15 @@ const HOME_LEFT_PANE_BOTTOM_PADDING = 16
|
|
|
52
51
|
const HOME_HERO_SIDE_ROOM = 16
|
|
53
52
|
|
|
54
53
|
export interface HomeLayout {
|
|
55
|
-
// The
|
|
54
|
+
// The window is landscape: the two-pane layout is in effect (for a pack with previews).
|
|
56
55
|
isWide: boolean
|
|
57
|
-
// The window's
|
|
56
|
+
// The window's size.
|
|
58
57
|
windowWidth: number
|
|
59
58
|
windowHeight: number
|
|
60
|
-
//
|
|
59
|
+
// The safe-area insets.
|
|
61
60
|
insets: EdgeInsets
|
|
62
|
-
// Wide layout: padding on the two panes' row. Each side hugs ITS OWN inset (a phone
|
|
63
|
-
//
|
|
61
|
+
// Wide layout: padding on the two panes' row. Each side hugs ITS OWN inset (a landscape phone has a
|
|
62
|
+
// large inset only on the island side), the trailing one also reserves the cog's gutter, and
|
|
64
63
|
// both carry the equal margin left over once the content hits its cap. 0 upright.
|
|
65
64
|
paddingLeft: number
|
|
66
65
|
paddingRight: number
|
|
@@ -118,12 +117,9 @@ export function computeHomeLayout(windowWidth: number, windowHeight: number, ins
|
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
// The live layout for the current window
|
|
122
|
-
// rotateInsets() rather than raw useWindowDimensions()/useSafeAreaInsets(), so it describes the
|
|
123
|
-
// footprint a rotated Home actually renders into (both are exact identities at 0deg).
|
|
120
|
+
// The live layout for the current window.
|
|
124
121
|
export function useHomeLayout(): HomeLayout {
|
|
125
|
-
const { width, height } =
|
|
126
|
-
const
|
|
127
|
-
const insets = rotateInsets(useSafeAreaInsets(), rotation)
|
|
122
|
+
const { width, height } = useWindowDimensions()
|
|
123
|
+
const insets = useSafeAreaInsets()
|
|
128
124
|
return computeHomeLayout(width, height, insets)
|
|
129
125
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ThemeAppearance, ThemeSettings } from '@rific/auto-paper'
|
|
2
|
-
import type { ViewRotation } from '@tastic/core'
|
|
3
2
|
import type { AlignResult, PopoverHost } from '@tastic/hud/chrome'
|
|
4
3
|
import type { GuideStep } from '@tastic/hud/guide'
|
|
5
4
|
import type { MenuOption } from '@tastic/hud/lobby'
|
|
@@ -39,10 +38,9 @@ export function humanSeatsFor(mode: RoundMode): Seat[] {
|
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
// What the loadout's match-options row is handed (see GameLoadout.options): the popover host it
|
|
42
|
-
// shares with seat 1's panel,
|
|
41
|
+
// shares with seat 1's panel, and the colors the row draws with.
|
|
43
42
|
export interface LoadoutOptionsProps {
|
|
44
43
|
host: PopoverHost
|
|
45
|
-
rotation: ViewRotation
|
|
46
44
|
accentColor: string
|
|
47
45
|
mutedColor: string
|
|
48
46
|
onAccentColor: string
|
|
@@ -58,7 +56,6 @@ export interface SeatControlLook {
|
|
|
58
56
|
seatColor: string
|
|
59
57
|
mutedColor: string
|
|
60
58
|
dark: boolean
|
|
61
|
-
rotation: ViewRotation
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
// One game's part of the shared loadout (shell/screens/LoadoutScreen.tsx), built by the game's
|
|
@@ -131,7 +128,7 @@ export interface PackGuide {
|
|
|
131
128
|
}
|
|
132
129
|
|
|
133
130
|
// A game's rules sheet (GameDefinition.Rules), opened from its tile's rules button on Home. It renders
|
|
134
|
-
// through a Paper Portal into a host Home mounts inside its
|
|
131
|
+
// through a Paper Portal into a host Home mounts inside its own frame.
|
|
135
132
|
export interface GameRulesProps {
|
|
136
133
|
visible: boolean
|
|
137
134
|
onDismiss: () => void
|
|
@@ -152,11 +149,10 @@ export interface GameDefinition {
|
|
|
152
149
|
// Seat 2's choices in this game, in menu order. 'nobody' only where the game has a solo mode.
|
|
153
150
|
// Left out by a game for one player only: its loadout has no seat 2 button and every round is solo.
|
|
154
151
|
seatTwo?: readonly SeatTwo[]
|
|
155
|
-
//
|
|
156
|
-
// to
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
twoPlayerSeats?: { faceToFaceOnWeb: boolean; followUpsideDown: boolean }
|
|
152
|
+
// The 2 Player loadout seats its players side by side (P1 on the left) in a window wider than tall,
|
|
153
|
+
// to match a game whose board does the same there. Unset, they sit face to face in any window (P1 at
|
|
154
|
+
// the bottom). Only read in 2 Player, so a game without 'person' among its seatTwo choices leaves it out.
|
|
155
|
+
sideBySideWhenWide?: boolean
|
|
160
156
|
// The store keys this game mounts (settings, achievements, guide), merged into the pack's store.
|
|
161
157
|
reducers: Record<string, Reducer>
|
|
162
158
|
// Mounted at the app root for the game's lifetime (its stats and achievements context). Left out by
|
|
@@ -172,9 +168,8 @@ export interface GameDefinition {
|
|
|
172
168
|
// board: the shell only remembers which game it was (redux/lastActiveGameSlice.ts). A match that
|
|
173
169
|
// isn't saved (liveplay) shouldn't opt in, or a relaunch would open a fresh one.
|
|
174
170
|
resumable?: boolean
|
|
175
|
-
// The play screen keeps the status bar
|
|
176
|
-
//
|
|
177
|
-
// content is turned, as on every other screen.
|
|
171
|
+
// The play screen keeps the status bar. Unset, the bar is hidden on every play screen for the whole
|
|
172
|
+
// round; set, it shows as on every other screen.
|
|
178
173
|
keepStatusBar?: boolean
|
|
179
174
|
// The game's stats sections and its achievement list for the stats screen, for one profile
|
|
180
175
|
// (null is Everyone).
|
|
@@ -313,13 +308,10 @@ export interface PackDefinition {
|
|
|
313
308
|
useScreenOptions?: () => StackScreenOptions
|
|
314
309
|
// The two sounds every button plays, in place of the shell's own.
|
|
315
310
|
useFeedbackSounds?: () => FeedbackSounds
|
|
316
|
-
// The pack's own toaster, drawn in a frame
|
|
317
|
-
//
|
|
311
|
+
// The pack's own toaster, drawn in a frame over the whole app (inside the safe area) in place of the
|
|
312
|
+
// shell's. It renders inline: the toast provider is given no
|
|
318
313
|
// Paper, so nothing portals it out of the frame.
|
|
319
314
|
Toaster?: ComponentType
|
|
320
|
-
// Every screen reads upright while the software keyboard is up (@tastic/core's OrientationProvider),
|
|
321
|
-
// for typing a profile's name with the phone sideways.
|
|
322
|
-
portraitWhileKeyboard?: boolean
|
|
323
315
|
// false leaves Edge Guard out of Settings (a pack with no edge controls and no Edge Guard plugin).
|
|
324
316
|
edgeGuard?: boolean
|
|
325
317
|
// The theme seat 1's color makes (a muted seed with a harmony), applied whenever the seats' colors or
|
|
@@ -352,9 +344,9 @@ export interface PackDefinition {
|
|
|
352
344
|
newProfileColor?: string
|
|
353
345
|
// Where a picker's menu opens (its alignOverride) on Home (seat 1's button), a loadout's one-human row
|
|
354
346
|
// (both seat buttons; 2 Player keeps each menu in its own player's half), the stats screen (its two
|
|
355
|
-
// pickers) and Profiles (a profile's color swatch). hud's own placement
|
|
356
|
-
//
|
|
357
|
-
//
|
|
347
|
+
// pickers) and Profiles (a profile's color swatch). It was added because hud's own placement measured
|
|
348
|
+
// a button in a frame turned by @tastic/core's drawn rotation against the upright window; nothing turns
|
|
349
|
+
// since core 0.15, so a pack may no longer need one.
|
|
358
350
|
// `open` is whether this picker's menu is open, the size is its content's, and `maxContentWidth` is a
|
|
359
351
|
// capped column's width the menu stays inside (Profiles' roster). Unset is hud's own placement.
|
|
360
352
|
usePickerAlign?: PickerAlign
|