@tastic/shell 0.6.0 → 0.8.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 +10 -9
- package/dist/index.d.mts +10 -6
- package/dist/index.d.ts +10 -6
- package/dist/index.js +146 -128
- package/dist/index.mjs +118 -100
- package/package.json +9 -9
- package/src/components/MatchChips.tsx +12 -12
- package/src/components/OpponentPicker.tsx +112 -0
- package/src/components/SettingsDialog.tsx +8 -3
- package/src/hooks/useAtComputer.ts +13 -0
- package/src/index.ts +2 -1
- package/src/redux/opponentSlice.ts +1 -1
- package/src/screens/HomeScreen.tsx +1 -1
- package/src/screens/LoadoutScreen.tsx +86 -37
- package/src/types.ts +16 -4
- package/src/components/SeatTwoPanel.tsx +0 -144
package/dist/index.mjs
CHANGED
|
@@ -460,7 +460,7 @@ var PROFILES_HREF = "/profiles";
|
|
|
460
460
|
import { defaultColors, getRgb, useAutoPaperTheme as useAutoPaperTheme3 } from "@rific/auto-paper";
|
|
461
461
|
import { useOrientationState } from "@tastic/core";
|
|
462
462
|
import { CornerActionButtons, PressAwayOverlay, usePopoverHost } from "@tastic/hud/chrome";
|
|
463
|
-
import { PlayerSetupPanel, ReadyButton
|
|
463
|
+
import { PlayerSetupPanel, ReadyButton, SharedActionBand } from "@tastic/hud/lobby";
|
|
464
464
|
import { DualZoneLayout, needsSharedNeutralZone, useDualZoneLayout } from "@tastic/split-screen";
|
|
465
465
|
import { router as router2, useFocusEffect as useFocusEffect2 } from "expo-router";
|
|
466
466
|
import { useCallback as useCallback9, useEffect as useEffect4, useMemo as useMemo5, useRef as useRef4, useState as useState4 } from "react";
|
|
@@ -469,80 +469,55 @@ import Animated from "react-native-reanimated";
|
|
|
469
469
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
470
470
|
import { useDispatch as useDispatch9 } from "react-redux";
|
|
471
471
|
|
|
472
|
-
// src/components/
|
|
473
|
-
import {
|
|
472
|
+
// src/components/OpponentPicker.tsx
|
|
473
|
+
import { useZoneClampedAlign } from "@tastic/hud/lobby";
|
|
474
474
|
import { getOptionPickerContentSize, OptionPicker } from "@tastic/profile";
|
|
475
475
|
import { useMemo as useMemo4 } from "react";
|
|
476
476
|
import { StyleSheet as StyleSheet2, View as View2 } from "react-native";
|
|
477
|
-
import { jsx as jsx10
|
|
477
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
478
478
|
var CPU_PREFIX = "cpu:";
|
|
479
|
-
var PROFILE_PREFIX = "profile:";
|
|
480
479
|
var NOBODY = "nobody";
|
|
481
|
-
var
|
|
482
|
-
var
|
|
480
|
+
var PERSON = "person";
|
|
481
|
+
var PERSON_ICON = "account-outline";
|
|
483
482
|
var NOBODY_ICON = "account-off-outline";
|
|
484
483
|
var CPU_ICON = "robot";
|
|
485
|
-
function
|
|
486
|
-
return `${idPrefix}-
|
|
484
|
+
function getOpponentPopoverId(idPrefix) {
|
|
485
|
+
return `${idPrefix}-opponent`;
|
|
487
486
|
}
|
|
488
|
-
function
|
|
489
|
-
const isPerson = seatTwo === "person";
|
|
487
|
+
function OpponentPicker({ idPrefix, host, choices, seatTwo, onSeatTwoChange, cpuDifficulty, color, onColorChange, swatches, takenColor, allowSwapTaken, dark, previewValue, triggerStyle, usePickerAlign }) {
|
|
490
488
|
const drawn = previewValue ? previewValue(color) : color;
|
|
491
|
-
const selectedProfile = isPerson ? profiles.find((p) => p.id === selectedProfileId) ?? null : null;
|
|
492
489
|
const cpuOptions = cpuDifficulty?.options;
|
|
493
490
|
const options = useMemo4(() => {
|
|
494
491
|
const list = [];
|
|
495
492
|
if (choices.includes("cpu")) list.push(...cpuOptions ? cpuOptions.map((o) => ({ value: `${CPU_PREFIX}${o.value}`, label: o.label, icon: o.icon })) : [{ value: CPU_PREFIX, label: "CPU", icon: CPU_ICON }]);
|
|
496
|
-
if (choices.includes("nobody")) list.push({ value: NOBODY, label: "
|
|
497
|
-
if (choices.includes("person")) {
|
|
498
|
-
list.push({ value: GUEST, label: "Player 2", icon: GUEST_ICON });
|
|
499
|
-
list.push(...[...profiles].sort((a, b) => a.name.localeCompare(b.name)).map((p) => ({ value: `${PROFILE_PREFIX}${p.id}`, label: p.name, icon: "account", color: previewValue ? previewValue(p.color) : p.color })));
|
|
500
|
-
}
|
|
493
|
+
if (choices.includes("nobody")) list.push({ value: NOBODY, label: "Solo", icon: NOBODY_ICON, dividerBefore: true });
|
|
494
|
+
if (choices.includes("person")) list.push({ value: PERSON, label: "Player", icon: PERSON_ICON, dividerBefore: true });
|
|
501
495
|
return list;
|
|
502
|
-
}, [choices, cpuOptions
|
|
503
|
-
const value = seatTwo === "cpu" ? `${CPU_PREFIX}${cpuDifficulty?.value ?? ""}` : seatTwo === "nobody" ? NOBODY :
|
|
504
|
-
const icon = seatTwo === "cpu" ? CPU_ICON : seatTwo === "nobody" ? NOBODY_ICON :
|
|
496
|
+
}, [choices, cpuOptions]);
|
|
497
|
+
const value = seatTwo === "cpu" ? `${CPU_PREFIX}${cpuDifficulty?.value ?? ""}` : seatTwo === "nobody" ? NOBODY : PERSON;
|
|
498
|
+
const icon = seatTwo === "cpu" ? CPU_ICON : seatTwo === "nobody" ? NOBODY_ICON : PERSON_ICON;
|
|
505
499
|
const handleChange = (next) => {
|
|
506
500
|
if (next.startsWith(CPU_PREFIX)) {
|
|
507
501
|
cpuDifficulty?.onChange(next.slice(CPU_PREFIX.length));
|
|
508
502
|
onSeatTwoChange("cpu");
|
|
509
|
-
} else if (next === NOBODY) {
|
|
510
|
-
onSeatTwoChange("nobody");
|
|
511
503
|
} else {
|
|
512
|
-
|
|
513
|
-
onSeatTwoChange("person");
|
|
504
|
+
onSeatTwoChange(next === NOBODY ? "nobody" : "person");
|
|
514
505
|
}
|
|
515
506
|
};
|
|
516
|
-
const colorSection = seatTwo === "
|
|
517
|
-
const pickerId =
|
|
518
|
-
const size = getOptionPickerContentSize(options
|
|
507
|
+
const colorSection = seatTwo === "cpu" ? { value: color, onChange: onColorChange, swatches, takenValue: takenColor, allowSwapTaken, previewValue } : void 0;
|
|
508
|
+
const pickerId = getOpponentPopoverId(idPrefix);
|
|
509
|
+
const size = getOptionPickerContentSize(options, colorSection);
|
|
519
510
|
const useAlign = usePickerAlign ?? useZoneClampedAlign;
|
|
520
511
|
const align = useAlign(host.openId === pickerId, size.width, size.height);
|
|
521
|
-
|
|
522
|
-
const pickerRowOpen = secondPickerId !== void 0 && host.openId === secondPickerId;
|
|
523
|
-
return /* @__PURE__ */ jsxs3(View2, { style: [styles2.panel, ownPopoverOpen && styles2.panelOpen], children: [
|
|
524
|
-
/* @__PURE__ */ jsx10(OptionPicker, { id: pickerId, host, options, value, onChange: handleChange, color: drawn, dark, icon, colorSection, alignOverride: align, triggerStyle }),
|
|
525
|
-
isPerson && secondPicker && /* @__PURE__ */ jsx10(View2, { style: [styles2.pickerRow, pickerRowOpen && styles2.pickerRowOpen], children: secondPicker }),
|
|
526
|
-
isPerson && showReadyButton && onToggleReady && /* @__PURE__ */ jsx10(ReadyButton, { color: drawn, ready: ready ?? false, onToggleReady })
|
|
527
|
-
] });
|
|
512
|
+
return /* @__PURE__ */ jsx10(View2, { style: [styles2.button, host.openId === pickerId && styles2.buttonOpen], children: /* @__PURE__ */ jsx10(OptionPicker, { id: pickerId, host, options, value, onChange: handleChange, color: drawn, dark, icon, colorSection, alignOverride: align, triggerStyle }) });
|
|
528
513
|
}
|
|
529
514
|
var styles2 = StyleSheet2.create({
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
},
|
|
534
|
-
// A popover escaping this panel needs the panel itself elevated to paint above a later sibling.
|
|
535
|
-
panelOpen: {
|
|
536
|
-
zIndex: 100
|
|
537
|
-
},
|
|
538
|
-
pickerRow: {
|
|
539
|
-
alignItems: "center",
|
|
540
|
-
flexDirection: "row",
|
|
541
|
-
gap: 12
|
|
515
|
+
// Its own column, so a row of seat buttons doesn't stretch the pill to the tallest of them.
|
|
516
|
+
button: {
|
|
517
|
+
alignItems: "center"
|
|
542
518
|
},
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
pickerRowOpen: {
|
|
519
|
+
// A popover escaping this button needs the button itself elevated to paint above a later sibling.
|
|
520
|
+
buttonOpen: {
|
|
546
521
|
zIndex: 100
|
|
547
522
|
}
|
|
548
523
|
});
|
|
@@ -582,18 +557,28 @@ function useAppSettings() {
|
|
|
582
557
|
}
|
|
583
558
|
|
|
584
559
|
// src/components/SettingsDialog.tsx
|
|
585
|
-
import { jsx as jsx11, jsxs as
|
|
560
|
+
import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
561
|
+
var noSettingsRules = () => null;
|
|
586
562
|
function SettingsDialog({ visible, onDismiss, onShowHowToPlay, children }) {
|
|
587
563
|
const onUpdateError = useUpdateErrorToast2();
|
|
588
564
|
const { settings, setSettings } = useAppSettings();
|
|
589
|
-
const { otaVersion, SettingsSections, edgeGuard = true } = usePack();
|
|
565
|
+
const { otaVersion, SettingsSections, edgeGuard = true, useSettingsRules = noSettingsRules } = usePack();
|
|
566
|
+
const rules = useSettingsRules();
|
|
590
567
|
const edgeGuardProps = edgeGuard ? { deferBottomEdgeGestures: settings.deferBottomEdgeGestures, onDeferBottomEdgeGestures: (value) => setSettings({ deferBottomEdgeGestures: value }) } : {};
|
|
591
|
-
return /* @__PURE__ */
|
|
568
|
+
return /* @__PURE__ */ jsxs3(BaseSettingsDialog, { visible, onDismiss, version: otaVersion, ...edgeGuardProps, onUpdateError, onShowHowToPlay, leading: rules, children: [
|
|
592
569
|
children,
|
|
593
570
|
SettingsSections && /* @__PURE__ */ jsx11(SettingsSections, {})
|
|
594
571
|
] });
|
|
595
572
|
}
|
|
596
573
|
|
|
574
|
+
// src/hooks/useAtComputer.ts
|
|
575
|
+
import { useIsTouchPrimaryDevice as useIsTouchPrimaryDevice2 } from "@tastic/core";
|
|
576
|
+
import { Platform as Platform3 } from "react-native";
|
|
577
|
+
function useAtComputer() {
|
|
578
|
+
const touchPrimary = useIsTouchPrimaryDevice2();
|
|
579
|
+
return Platform3.OS === "web" && !touchPrimary;
|
|
580
|
+
}
|
|
581
|
+
|
|
597
582
|
// src/hooks/useOpponent.ts
|
|
598
583
|
import { useCallback as useCallback7 } from "react";
|
|
599
584
|
import { useDispatch as useDispatch7, useSelector as useSelector6 } from "react-redux";
|
|
@@ -747,8 +732,10 @@ var liveplayActions = actions5;
|
|
|
747
732
|
var liveplaySlice_default = reducer5;
|
|
748
733
|
|
|
749
734
|
// src/screens/LoadoutScreen.tsx
|
|
750
|
-
import { jsx as jsx12, jsxs as
|
|
735
|
+
import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
751
736
|
var PANEL_SWAP_FADE_MS = 180;
|
|
737
|
+
var P1_CONTROL_ID = "p1-control";
|
|
738
|
+
var P2_CONTROL_ID = "p2-control";
|
|
752
739
|
function LoadoutScreen({ game }) {
|
|
753
740
|
const pack = usePack();
|
|
754
741
|
const { seatTwo: savedSeatTwo, setSeatTwo } = useOpponent();
|
|
@@ -761,7 +748,9 @@ function LoadoutScreen({ game }) {
|
|
|
761
748
|
const { openFor } = useGameGuide();
|
|
762
749
|
const { profiles, lastSelected, selectProfile } = useProfiles();
|
|
763
750
|
const { orientationMode } = useOrientationState();
|
|
764
|
-
const
|
|
751
|
+
const atComputer = useAtComputer();
|
|
752
|
+
const twoPlayerMode = game.sideBySideAtComputer && atComputer ? "sideBySide" : game.sideBySideWhenWide ? orientationMode : "faceToFace";
|
|
753
|
+
const panelMode = p2IsHuman ? twoPlayerMode : orientationMode;
|
|
765
754
|
const { panelLayout, panelFadeStyle } = useDualZoneLayout(panelMode, false, true, false, PANEL_SWAP_FADE_MS);
|
|
766
755
|
const playersRowFade = panelFadeStyle;
|
|
767
756
|
const [settingsOpen, setSettingsOpen] = useState4(false);
|
|
@@ -770,7 +759,7 @@ function LoadoutScreen({ game }) {
|
|
|
770
759
|
const { p1Color, p2Color, handleP1ColorChange, handleP2ColorChange } = useSeatColors(p2IsHuman);
|
|
771
760
|
const dispatch = useDispatch9();
|
|
772
761
|
const handleP1ProfileSelect = useCallback9((profile) => selectProfile(1, profile?.id ?? null), [selectProfile]);
|
|
773
|
-
const handleP2ProfileSelect = useCallback9((
|
|
762
|
+
const handleP2ProfileSelect = useCallback9((profile) => selectProfile(2, profile?.id ?? null), [selectProfile]);
|
|
774
763
|
const [ready, setReady] = useState4({ 1: false, 2: false });
|
|
775
764
|
useFocusEffect2(
|
|
776
765
|
useCallback9(() => {
|
|
@@ -801,16 +790,16 @@ function LoadoutScreen({ game }) {
|
|
|
801
790
|
const p1Drawn = seatPreview ? seatPreview(p1Color) : p1Color;
|
|
802
791
|
const p2Drawn = seatPreview ? seatPreview(p2Color) : p2Color;
|
|
803
792
|
const triggerStyle = useMemo5(() => pack.Backdrop ? [styles3.pill, { backgroundColor: themeColors.surface }] : void 0, [pack.Backdrop, themeColors.surface]);
|
|
793
|
+
const readySurface = pack.Backdrop ? themeColors.surface : void 0;
|
|
804
794
|
const isPortrait = panelLayout.orientationMode === "faceToFace";
|
|
805
795
|
const showMetaInSharedBand = needsSharedNeutralZone(panelLayout.orientationMode, humanSeats.length);
|
|
806
796
|
const p2ZoneStyle = p2IsHuman ? isPortrait ? styles3.p2ZoneTop : styles3.p2ZoneRight : null;
|
|
807
|
-
const showReadyButton = p2IsHuman;
|
|
808
797
|
const seatPickerAlign = p2IsHuman ? void 0 : pack.usePickerAlign;
|
|
809
798
|
const layoutKey = p2IsHuman ? "zones" : "row";
|
|
810
799
|
const controlsHost = usePopoverHost();
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
const
|
|
800
|
+
const p2Host = usePopoverHost();
|
|
801
|
+
const p1PickerOpen = controlsHost.openId?.startsWith("p1-") ?? false;
|
|
802
|
+
const p1ControlOpen = controlsHost.openId === P1_CONTROL_ID;
|
|
814
803
|
const optionsRow = loadout.options({
|
|
815
804
|
host: controlsHost,
|
|
816
805
|
accentColor: themeColors.tertiary,
|
|
@@ -825,11 +814,23 @@ function LoadoutScreen({ game }) {
|
|
|
825
814
|
const sharedControls = showMetaInSharedBand ? /* @__PURE__ */ jsx12(SharedActionBand, { onBack: safeBack, onSettings: () => setSettingsOpen(true), onRandomize: loadout.randomize, onReset: loadout.reset, fg, popoverOpen: optionsPopoverOpen, children: optionsRow }) : optionsRow;
|
|
826
815
|
const controlLook = { accentColor: themeColors.tertiary, mutedColor: fgMuted, dark };
|
|
827
816
|
const p1Control = loadout.seatControl(1, controlsHost, { ...controlLook, seatColor: p1Drawn });
|
|
828
|
-
const p1Panel = /* @__PURE__ */ jsx12(PlayerSetupPanel, { idPrefix: "p1", host: controlsHost, color: p1Color, onColorChange: handleP1ColorChange, swatches: defaultColors, takenColor: p2Exists ? p2Color : void 0, allowSwapTaken: mode === "vsCpu", colorCaption: pack.colorCaption, isHuman: true,
|
|
817
|
+
const p1Panel = /* @__PURE__ */ jsx12(PlayerSetupPanel, { idPrefix: "p1", host: controlsHost, color: p1Color, onColorChange: handleP1ColorChange, swatches: defaultColors, takenColor: p2Exists ? p2Color : void 0, allowSwapTaken: mode === "vsCpu", colorCaption: pack.colorCaption, isHuman: true, dark, showReadyButton: false, profiles, selectedProfileId: lastSelected[1], takenProfileId: p2IsHuman ? lastSelected[2] : null, guestLabel: pack.guestLabel ?? "P1", nullLabel: pack.nullLabel, onProfileSelect: handleP1ProfileSelect, onManageProfiles: () => router2.push(PROFILES_HREF), previewValue: seatPreview, triggerStyle, useSeatPickerAlign: seatPickerAlign }, `p1-${layoutKey}`);
|
|
818
|
+
const opponentPicker = game.seatTwo && /* @__PURE__ */ jsx12(OpponentPicker, { idPrefix: "p1", host: controlsHost, choices: game.seatTwo, seatTwo, onSeatTwoChange: changeSeatTwo, cpuDifficulty: loadout.cpuDifficulty, color: p2Color, onColorChange: handleP2ColorChange, swatches: defaultColors, takenColor: p1Color, allowSwapTaken: mode === "vsCpu", dark, previewValue: seatPreview, triggerStyle, usePickerAlign: seatPickerAlign }, `opponent-${layoutKey}`);
|
|
819
|
+
const toggleP1Ready = () => setReady((r) => ({ ...r, 1: !r[1] }));
|
|
820
|
+
const seatOne = /* @__PURE__ */ jsxs4(View3, { style: [styles3.seatOne, !p2IsHuman && styles3.seatOneAlone, p1PickerOpen && styles3.raised], children: [
|
|
821
|
+
/* @__PURE__ */ jsxs4(View3, { style: [styles3.seatOneButtons, p1PickerOpen && styles3.raised], children: [
|
|
822
|
+
/* @__PURE__ */ jsxs4(Animated.View, { style: [styles3.playersRow, p1PickerOpen && styles3.raised, !p2IsHuman && playersRowFade], children: [
|
|
823
|
+
p1Panel,
|
|
824
|
+
opponentPicker
|
|
825
|
+
] }),
|
|
826
|
+
p1Control && /* @__PURE__ */ jsx12(View3, { style: [styles3.controlRow, p1ControlOpen && styles3.raised], children: p1Control })
|
|
827
|
+
] }),
|
|
828
|
+
/* @__PURE__ */ jsx12(ReadyButton, { color: p1Drawn, ready: ready[1], onToggleReady: toggleP1Ready, surfaceColor: readySurface, dark, style: !p2IsHuman && p1PickerOpen && styles3.readyButtonHidden })
|
|
829
|
+
] });
|
|
829
830
|
const p2Control = p2IsHuman ? loadout.seatControl(2, p2Host, { ...controlLook, seatColor: p2Drawn }) : void 0;
|
|
830
|
-
const p2Panel =
|
|
831
|
-
return /* @__PURE__ */
|
|
832
|
-
/* @__PURE__ */
|
|
831
|
+
const p2Panel = p2IsHuman && /* @__PURE__ */ jsx12(PlayerSetupPanel, { idPrefix: "p2", host: p2Host, color: p2Color, onColorChange: handleP2ColorChange, swatches: defaultColors, takenColor: p1Color, colorCaption: pack.colorCaption, isHuman: true, ready: ready[2], onToggleReady: () => setReady((r) => ({ ...r, 2: !r[2] })), dark, secondPicker: p2Control, secondPickerId: p2Control ? P2_CONTROL_ID : void 0, profiles, selectedProfileId: lastSelected[2], takenProfileId: lastSelected[1], guestLabel: pack.guestLabel ?? "P2", nullLabel: pack.nullLabel, onProfileSelect: handleP2ProfileSelect, previewValue: seatPreview, triggerStyle, readySurfaceColor: readySurface }, "p2-zones");
|
|
832
|
+
return /* @__PURE__ */ jsxs4(View3, { style: [styles3.container, { backgroundColor: bg }], children: [
|
|
833
|
+
/* @__PURE__ */ jsxs4(View3, { style: styles3.frame, children: [
|
|
833
834
|
/* @__PURE__ */ jsx12(PressAwayOverlay, { active: controlsHost.openId !== null, onPress: controlsHost.close }),
|
|
834
835
|
p2ZoneStyle && /* @__PURE__ */ jsx12(PressAwayOverlay, { active: controlsHost.openId !== null || p2Host.openId !== null, onPress: p2Host.close, style: p2ZoneStyle }),
|
|
835
836
|
!showMetaInSharedBand && /* @__PURE__ */ jsx12(CornerActionButtons, { onBack: safeBack, onSettings: () => setSettingsOpen(true), fg, insets }),
|
|
@@ -838,20 +839,16 @@ function LoadoutScreen({ game }) {
|
|
|
838
839
|
{
|
|
839
840
|
panelLayout,
|
|
840
841
|
panelFadeStyle,
|
|
841
|
-
p1:
|
|
842
|
+
p1: seatOne,
|
|
842
843
|
p2: p2Panel,
|
|
843
844
|
shared: sharedControls,
|
|
844
|
-
p1Elevated:
|
|
845
|
+
p1Elevated: p1PickerOpen,
|
|
845
846
|
p2Elevated: p2Host.openId !== null,
|
|
846
847
|
sharedElevated: optionsPopoverOpen
|
|
847
848
|
}
|
|
848
|
-
) : /* @__PURE__ */
|
|
849
|
+
) : /* @__PURE__ */ jsxs4(View3, { style: styles3.stackedZone, children: [
|
|
849
850
|
sharedControls,
|
|
850
|
-
|
|
851
|
-
p1Panel,
|
|
852
|
-
p2Panel
|
|
853
|
-
] }),
|
|
854
|
-
/* @__PURE__ */ jsx12(ReadyButton2, { color: p1Drawn, ready: ready[1], onToggleReady: () => setReady((r) => ({ ...r, 1: !r[1] })), style: anyPlayerPickerOpen && styles3.readyButtonHidden })
|
|
851
|
+
seatOne
|
|
855
852
|
] })
|
|
856
853
|
] }),
|
|
857
854
|
/* @__PURE__ */ jsx12(SettingsDialog, { visible: settingsOpen, onDismiss: () => setSettingsOpen(false), onShowHowToPlay: () => openFor(game.id) })
|
|
@@ -865,6 +862,12 @@ var styles3 = StyleSheet3.create({
|
|
|
865
862
|
container: {
|
|
866
863
|
flex: 1
|
|
867
864
|
},
|
|
865
|
+
// Player 1's controls, under their row of buttons.
|
|
866
|
+
controlRow: {
|
|
867
|
+
alignItems: "center",
|
|
868
|
+
flexDirection: "row",
|
|
869
|
+
gap: 12
|
|
870
|
+
},
|
|
868
871
|
// A real full-bleed box to size the absolutely-positioned children against.
|
|
869
872
|
frame: {
|
|
870
873
|
alignItems: "center",
|
|
@@ -891,21 +894,37 @@ var styles3 = StyleSheet3.create({
|
|
|
891
894
|
pill: {
|
|
892
895
|
borderRadius: 999
|
|
893
896
|
},
|
|
894
|
-
//
|
|
895
|
-
//
|
|
897
|
+
// Seat 1's button and the opponent button read as one evenly spaced row of buttons, at
|
|
898
|
+
// PlayerSetupPanel's own pickerRow gap (12).
|
|
896
899
|
playersRow: {
|
|
897
900
|
flexDirection: "row",
|
|
898
901
|
gap: 12
|
|
899
902
|
},
|
|
900
|
-
|
|
903
|
+
// Lifts whichever piece of player 1's has a menu open above its later siblings (each View is its
|
|
904
|
+
// own stacking context on the web).
|
|
905
|
+
raised: {
|
|
901
906
|
zIndex: 100
|
|
902
907
|
},
|
|
903
|
-
// See
|
|
908
|
+
// See p1PickerOpen: opacity 0 (not display:'none') keeps the button's layout space, and
|
|
904
909
|
// pointerEvents:'none' keeps the sliver an open menu doesn't cover from being a hidden tap target.
|
|
905
910
|
readyButtonHidden: {
|
|
906
911
|
opacity: 0,
|
|
907
912
|
pointerEvents: "none"
|
|
908
913
|
},
|
|
914
|
+
// Player 1's column: their row of buttons and controls, then Ready, at PlayerSetupPanel's own
|
|
915
|
+
// rhythm in their half of 2 Player.
|
|
916
|
+
seatOne: {
|
|
917
|
+
alignItems: "center",
|
|
918
|
+
gap: 12
|
|
919
|
+
},
|
|
920
|
+
// With one person, Ready sits as far under the buttons as the match options sit above them.
|
|
921
|
+
seatOneAlone: {
|
|
922
|
+
gap: 28
|
|
923
|
+
},
|
|
924
|
+
seatOneButtons: {
|
|
925
|
+
alignItems: "center",
|
|
926
|
+
gap: 12
|
|
927
|
+
},
|
|
909
928
|
stackedZone: {
|
|
910
929
|
alignItems: "center",
|
|
911
930
|
gap: 28
|
|
@@ -1031,7 +1050,7 @@ function useHomeLayout() {
|
|
|
1031
1050
|
}
|
|
1032
1051
|
|
|
1033
1052
|
// src/screens/HomeScreen.tsx
|
|
1034
|
-
import { Fragment as Fragment4, jsx as jsx14, jsxs as
|
|
1053
|
+
import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1035
1054
|
function HomeScreen() {
|
|
1036
1055
|
const pack = usePack();
|
|
1037
1056
|
const { consumeResumeGame } = useLastActiveGame();
|
|
@@ -1099,19 +1118,19 @@ function HomeMenu() {
|
|
|
1099
1118
|
const picker = /* @__PURE__ */ jsx14(ProfilePicker, { idPrefix: SEAT_ONE_PICKER, host: pickerHost, profiles, selectedId: lastSelected[1], color: p1Drawn, dark, guestLabel: pack.guestLabel ?? "P1", nullLabel: pack.nullLabel, onSelect: (profile) => selectProfile(1, profile?.id ?? null), onManage: () => router3.push(PROFILES_HREF), colorSection, alignOverride: pickerAlign, triggerStyle });
|
|
1100
1119
|
const pressAway = /* @__PURE__ */ jsx14(PressAwayOverlay2, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles4.pressAway });
|
|
1101
1120
|
const RulesSheet = rulesGame?.Rules;
|
|
1102
|
-
return /* @__PURE__ */
|
|
1103
|
-
/* @__PURE__ */ jsx14(View4, { style: styles4.frame, children: /* @__PURE__ */
|
|
1104
|
-
isWide ? /* @__PURE__ */
|
|
1121
|
+
return /* @__PURE__ */ jsxs5(View4, { style: [styles4.container, { backgroundColor: bg }], children: [
|
|
1122
|
+
/* @__PURE__ */ jsx14(View4, { style: styles4.frame, children: /* @__PURE__ */ jsxs5(RulesHost, { enabled: pack.games.some((game) => game.Rules), children: [
|
|
1123
|
+
isWide ? /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
1105
1124
|
pressAway,
|
|
1106
|
-
/* @__PURE__ */
|
|
1125
|
+
/* @__PURE__ */ jsxs5(FadingScrollView, { ...tab && gridClamp, style: [styles4.wideRightPane, { left: layout.paddingLeft + layout.leftPaneWidth + HOME_WIDE_PANE_GAP }], contentContainerStyle: { paddingTop: 16 + insets.top, paddingBottom: 16 + insets.bottom, paddingRight: layout.paddingRight }, children: [
|
|
1107
1126
|
tabBar && /* @__PURE__ */ jsx14(View4, { style: [styles4.wideTabs, { width: layout.rightPaneWidth }], children: tabBar }),
|
|
1108
1127
|
/* @__PURE__ */ jsx14(View4, { style: [styles4.grid, { width: layout.rightPaneWidth }], children: pack.games.map((game) => /* @__PURE__ */ jsx14(GameTile, { game, hidden: isHidden(tab, game), width: layout.tileWidth, previewWidth: layout.previewWidth, previewRevealed: previewsRevealed, dark, p1Color, p2Color, onPress: () => openGame(game), onRules: hasRules(game) ? () => openRules(game) : void 0 }, game.id)) })
|
|
1109
1128
|
] }),
|
|
1110
|
-
/* @__PURE__ */
|
|
1129
|
+
/* @__PURE__ */ jsxs5(View4, { style: [styles4.wideLeftPane, { left: layout.paddingLeft, width: layout.leftPaneWidth, paddingTop: HOME_TOP_BAND + insets.top, paddingBottom: 16 + insets.bottom }, pickerHost.openId !== null && styles4.pickerOpen], children: [
|
|
1111
1130
|
/* @__PURE__ */ jsx14(pack.HeroTitle, { words: pack.wordmark, color: fg, p1Color, p2Color, active: heroActive, fit: layout.heroFit }),
|
|
1112
1131
|
/* @__PURE__ */ jsx14(View4, { style: styles4.widePicker, children: picker })
|
|
1113
1132
|
] })
|
|
1114
|
-
] }) : /* @__PURE__ */
|
|
1133
|
+
] }) : /* @__PURE__ */ jsxs5(FadingScrollView, { ...tab && columnClamp, style: styles4.scroll, contentContainerStyle: [styles4.content, { paddingTop: 56 + insets.top, paddingBottom: 24 + insets.bottom, paddingLeft: 16 + insets.left, paddingRight: 16 + insets.right }], children: [
|
|
1115
1134
|
pressAway,
|
|
1116
1135
|
/* @__PURE__ */ jsx14(pack.HeroTitle, { words: pack.wordmark, color: fg, p1Color, p2Color, active: heroActive }),
|
|
1117
1136
|
/* @__PURE__ */ jsx14(View4, { style: [styles4.picker, pickerHost.openId !== null && styles4.pickerOpen], children: picker }),
|
|
@@ -1167,10 +1186,10 @@ function GameRow({ game, hidden, dark, p1Color, p2Color, previewRevealed, onPres
|
|
|
1167
1186
|
return (
|
|
1168
1187
|
// The rules button sits beside the row's own button, laid over its end, not inside it: a button
|
|
1169
1188
|
// inside a button is invalid on the web.
|
|
1170
|
-
/* @__PURE__ */
|
|
1171
|
-
/* @__PURE__ */
|
|
1189
|
+
/* @__PURE__ */ jsxs5(View4, { style: hidden ? styles4.hidden : void 0, children: [
|
|
1190
|
+
/* @__PURE__ */ jsxs5(Pressable, { onPress, style: [styles4.row, { backgroundColor: well.bg }], accessibilityRole: "button", accessibilityLabel: game.title, children: [
|
|
1172
1191
|
game.Preview ? /* @__PURE__ */ jsx14(PreviewBox, { game, width: ROW_PREVIEW_WIDTH, revealed: previewRevealed, dark, p1Color, p2Color }) : /* @__PURE__ */ jsx14(View4, { style: [styles4.rowIcon, { backgroundColor: dark ? ICON_DARK_BG : ICON_LIGHT_BG }], children: GameIcon && /* @__PURE__ */ jsx14(GameIcon, { size: ROW_ICON_SIZE, p1Color, p2Color, dark }) }),
|
|
1173
|
-
/* @__PURE__ */
|
|
1192
|
+
/* @__PURE__ */ jsxs5(View4, { style: styles4.rowText, children: [
|
|
1174
1193
|
/* @__PURE__ */ jsx14(Text, { variant: "titleLarge", style: [styles4.rowTitle, { color: well.fg }], numberOfLines: 1, children: game.title }),
|
|
1175
1194
|
/* @__PURE__ */ jsx14(Text, { variant: "bodyMedium", style: { color: well.fgMuted }, numberOfLines: 2, children: game.blurb })
|
|
1176
1195
|
] })
|
|
@@ -1181,8 +1200,8 @@ function GameRow({ game, hidden, dark, p1Color, p2Color, previewRevealed, onPres
|
|
|
1181
1200
|
}
|
|
1182
1201
|
function GameTile({ game, hidden, width, previewWidth, dark, p1Color, p2Color, previewRevealed, onPress, onRules }) {
|
|
1183
1202
|
const well = useWellColors();
|
|
1184
|
-
return /* @__PURE__ */
|
|
1185
|
-
/* @__PURE__ */
|
|
1203
|
+
return /* @__PURE__ */ jsxs5(View4, { style: [{ width }, hidden && styles4.hidden], children: [
|
|
1204
|
+
/* @__PURE__ */ jsxs5(Pressable, { onPress, style: [styles4.tile, { backgroundColor: well.bg }], accessibilityRole: "button", accessibilityLabel: game.title, children: [
|
|
1186
1205
|
/* @__PURE__ */ jsx14(PreviewBox, { game, width: previewWidth, revealed: previewRevealed, dark, p1Color, p2Color }),
|
|
1187
1206
|
/* @__PURE__ */ jsx14(Text, { variant: "titleMedium", style: [styles4.rowTitle, { color: well.fg }], numberOfLines: 1, children: game.title }),
|
|
1188
1207
|
/* @__PURE__ */ jsx14(Text, { variant: "bodySmall", style: { color: well.fgMuted }, numberOfLines: 3, children: game.blurb })
|
|
@@ -1368,7 +1387,7 @@ import { StyleSheet as StyleSheet5, View as View5 } from "react-native";
|
|
|
1368
1387
|
import { SegmentedButtons as SegmentedButtons2, Text as Text2 } from "react-native-paper";
|
|
1369
1388
|
import { useSafeAreaInsets as useSafeAreaInsets3 } from "react-native-safe-area-context";
|
|
1370
1389
|
import { useDispatch as useDispatch10 } from "react-redux";
|
|
1371
|
-
import { jsx as jsx16, jsxs as
|
|
1390
|
+
import { jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1372
1391
|
var STATS = "stats";
|
|
1373
1392
|
var ACHIEVEMENTS = "achievements";
|
|
1374
1393
|
var TABS = [
|
|
@@ -1421,10 +1440,10 @@ function StatsContent({ pickerHost }) {
|
|
|
1421
1440
|
const surface = pack.Backdrop ? { backgroundColor: colors.surface } : void 0;
|
|
1422
1441
|
const triggerStyle = pack.Backdrop ? [styles5.pill, surface] : void 0;
|
|
1423
1442
|
const PackTabContent = packTab?.Content;
|
|
1424
|
-
return /* @__PURE__ */
|
|
1443
|
+
return /* @__PURE__ */ jsxs6(BaseStatsScreen, { title, onBack: safeBack, insets, onReset: reset.onReset, resetLabel: reset.label, resetConfirmTitle: reset.title, resetConfirmBody: reset.body, fg: pack.foreground, bg: pack.Backdrop ? "transparent" : void 0, rotation: 0, children: [
|
|
1425
1444
|
/* @__PURE__ */ jsx16(PressAwayOverlay3, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles5.pressAway }),
|
|
1426
1445
|
/* @__PURE__ */ jsx16(SegmentedButtons2, { value: tab, onValueChange: setTab, buttons: tabButtons, style: [styles5.tabs, surface] }),
|
|
1427
|
-
/* @__PURE__ */
|
|
1446
|
+
/* @__PURE__ */ jsxs6(View5, { style: [styles5.filters, pickerHost.openId !== null && styles5.filtersOpen], onStartShouldSetResponder: () => pickerHost.openId !== null, children: [
|
|
1428
1447
|
profiles.length > 0 && /* @__PURE__ */ jsx16(OptionPicker2, { id: PLAYER_PICKER_ID, host: pickerHost, options: playerOptions, value: profileId ?? EVERYONE, onChange: (value) => setSelectedProfileId(value === EVERYONE ? null : value), color: colors.primary, dark, icon: profileId ? "account" : "account-group", alignOverride: playerAlign, triggerStyle }),
|
|
1429
1448
|
showGamePicker && /* @__PURE__ */ jsx16(OptionPicker2, { id: GAME_PICKER_ID, host: pickerHost, options: gameOptions, value: gameId, onChange: setGameId, color: colors.primary, dark, icon: "view-grid-outline", alignOverride: gameAlign, triggerStyle })
|
|
1430
1449
|
] }),
|
|
@@ -1445,7 +1464,7 @@ function GameStats({ game, tab, profileId, showTitle, titleColor }) {
|
|
|
1445
1464
|
const { StatsSections, AchievementList } = game;
|
|
1446
1465
|
const Sections = tab === STATS ? StatsSections : AchievementList;
|
|
1447
1466
|
if (!Sections) return null;
|
|
1448
|
-
return /* @__PURE__ */
|
|
1467
|
+
return /* @__PURE__ */ jsxs6(View5, { style: styles5.game, children: [
|
|
1449
1468
|
showTitle && /* @__PURE__ */ jsx16(Text2, { variant: "titleLarge", style: [styles5.gameTitle, { color: titleColor }], children: game.title }),
|
|
1450
1469
|
/* @__PURE__ */ jsx16(Sections, { profileId })
|
|
1451
1470
|
] });
|
|
@@ -1496,20 +1515,19 @@ function imageGameIcon(images) {
|
|
|
1496
1515
|
import { useAutoPaperTheme as useAutoPaperTheme6 } from "@rific/auto-paper";
|
|
1497
1516
|
import { IconButton as IconButton2 } from "@rific/feedback-press";
|
|
1498
1517
|
import { useEffect as useEffect7 } from "react";
|
|
1499
|
-
import {
|
|
1518
|
+
import { StyleSheet as StyleSheet6, useWindowDimensions as useWindowDimensions2 } from "react-native";
|
|
1500
1519
|
import Animated2, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
|
|
1501
1520
|
import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
|
|
1502
|
-
import { Fragment as Fragment5, jsx as jsx18, jsxs as
|
|
1503
|
-
var MATCH_CHIPS_DESKTOP_MIN_WIDTH = 768;
|
|
1521
|
+
import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1504
1522
|
var MATCH_CHIPS_FADE_MS = 150;
|
|
1505
1523
|
var CHIP_SIZE = 48;
|
|
1506
1524
|
var EDGE_PAD = 8;
|
|
1507
1525
|
function MatchChips({ playing, faceToFace, centerLineY, onBack, onSettings }) {
|
|
1508
|
-
const {
|
|
1526
|
+
const { height } = useWindowDimensions2();
|
|
1509
1527
|
const insets = useSafeAreaInsets4();
|
|
1510
1528
|
const { dark } = useAutoPaperTheme6();
|
|
1511
|
-
const
|
|
1512
|
-
const visible =
|
|
1529
|
+
const atComputer = useAtComputer();
|
|
1530
|
+
const visible = atComputer || !playing;
|
|
1513
1531
|
const opacity = useSharedValue(visible ? 1 : 0);
|
|
1514
1532
|
useEffect7(() => {
|
|
1515
1533
|
opacity.value = withTiming(visible ? 1 : 0, { duration: MATCH_CHIPS_FADE_MS });
|
|
@@ -1517,12 +1535,12 @@ function MatchChips({ playing, faceToFace, centerLineY, onBack, onSettings }) {
|
|
|
1517
1535
|
const fadeStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
|
|
1518
1536
|
const iconColor = dark ? "#FFFFFF" : "#000000";
|
|
1519
1537
|
const containerColor = dark ? "rgba(0,0,0,0.55)" : "rgba(255,255,255,0.55)";
|
|
1520
|
-
const between = faceToFace && !
|
|
1538
|
+
const between = faceToFace && !atComputer;
|
|
1521
1539
|
const top = between ? (centerLineY ?? height / 2) - CHIP_SIZE / 2 : EDGE_PAD + insets.top;
|
|
1522
1540
|
const backSlot = { top, left: EDGE_PAD + insets.left };
|
|
1523
1541
|
const settingsSlot = { top, right: EDGE_PAD + insets.right };
|
|
1524
1542
|
const pointerEvents = visible ? "box-none" : "none";
|
|
1525
|
-
return /* @__PURE__ */
|
|
1543
|
+
return /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
1526
1544
|
/* @__PURE__ */ jsx18(Animated2.View, { pointerEvents, style: [styles6.slot, backSlot, fadeStyle], testID: "match-chips-back", children: /* @__PURE__ */ jsx18(IconButton2, { icon: "arrow-left", iconColor, containerColor, style: styles6.chip, size: 24, onPress: onBack, accessibilityLabel: "Back" }) }),
|
|
1527
1545
|
/* @__PURE__ */ jsx18(Animated2.View, { pointerEvents, style: [styles6.slot, settingsSlot, fadeStyle], testID: "match-chips-settings", children: /* @__PURE__ */ jsx18(IconButton2, { icon: "cog", iconColor, containerColor, style: styles6.chip, size: 24, onPress: onSettings, accessibilityLabel: "Settings" }) })
|
|
1528
1546
|
] });
|
|
@@ -1787,7 +1805,6 @@ export {
|
|
|
1787
1805
|
GUEST_PLAYER_ID,
|
|
1788
1806
|
HomeScreen,
|
|
1789
1807
|
LoadoutRoute,
|
|
1790
|
-
MATCH_CHIPS_DESKTOP_MIN_WIDTH,
|
|
1791
1808
|
MATCH_CHIPS_FADE_MS,
|
|
1792
1809
|
MatchChips,
|
|
1793
1810
|
PREVIEW_ASPECT,
|
|
@@ -1825,6 +1842,7 @@ export {
|
|
|
1825
1842
|
seatTwoFor,
|
|
1826
1843
|
useActiveRound,
|
|
1827
1844
|
useAppSettings,
|
|
1845
|
+
useAtComputer,
|
|
1828
1846
|
useGameGuide,
|
|
1829
1847
|
useOpponent,
|
|
1830
1848
|
usePack,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The app around a pack of React Native games, two-seat or for one player: Home, the loadout, the stats screen, Profiles and Settings, the pack's Redux store and its root providers, driven by one PackDefinition",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -70,12 +70,12 @@
|
|
|
70
70
|
"@expo/vector-icons": "^15.1.1",
|
|
71
71
|
"@infinitetoken/eslint-config": "^0.2.1",
|
|
72
72
|
"@infinitetoken/jest-config": "^0.5.2",
|
|
73
|
-
"@infinitetoken/tsconfig": "^0.
|
|
73
|
+
"@infinitetoken/tsconfig": "^0.7.0",
|
|
74
74
|
"@react-native-async-storage/async-storage": "2.2.0",
|
|
75
75
|
"@react-native-masked-view/masked-view": "0.3.2",
|
|
76
76
|
"@react-native/jest-preset": "^0.88.0",
|
|
77
77
|
"@reduxjs/toolkit": "^2.14.0",
|
|
78
|
-
"@rific/auto-paper": "^0.
|
|
78
|
+
"@rific/auto-paper": "^0.14.0",
|
|
79
79
|
"@rific/core": "^0.5.0",
|
|
80
80
|
"@rific/feedback-press": "^0.13.1",
|
|
81
81
|
"@rific/focus-chain": "^0.4.8",
|
|
@@ -84,10 +84,10 @@
|
|
|
84
84
|
"@rific/toaster": "^0.12.0",
|
|
85
85
|
"@rific/updater": "^0.8.0",
|
|
86
86
|
"@tastic/achievements": "^0.6.0",
|
|
87
|
-
"@tastic/core": "^0.
|
|
87
|
+
"@tastic/core": "^0.16.0",
|
|
88
88
|
"@tastic/edge-guard": "^0.1.5",
|
|
89
|
-
"@tastic/hud": "^0.
|
|
90
|
-
"@tastic/profile": "^0.
|
|
89
|
+
"@tastic/hud": "^0.29.0",
|
|
90
|
+
"@tastic/profile": "^0.18.0",
|
|
91
91
|
"@tastic/split-screen": "^0.5.5",
|
|
92
92
|
"@testing-library/react-native": "^14.1.0",
|
|
93
93
|
"@types/jest": "30.0.0",
|
|
@@ -135,10 +135,10 @@
|
|
|
135
135
|
"@rific/splash-gate": ">=0.2.3",
|
|
136
136
|
"@rific/toaster": ">=0.10.1",
|
|
137
137
|
"@tastic/achievements": ">=0.6.0",
|
|
138
|
-
"@tastic/core": ">=0.
|
|
138
|
+
"@tastic/core": ">=0.16.0",
|
|
139
139
|
"@tastic/edge-guard": ">=0.1.5",
|
|
140
|
-
"@tastic/hud": ">=0.
|
|
141
|
-
"@tastic/profile": ">=0.
|
|
140
|
+
"@tastic/hud": ">=0.29.0",
|
|
141
|
+
"@tastic/profile": ">=0.18.0",
|
|
142
142
|
"@tastic/split-screen": ">=0.5.5",
|
|
143
143
|
"expo-blur": ">=58.0.4",
|
|
144
144
|
"expo-constants": ">=58.0.12",
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
2
|
import { IconButton } from '@rific/feedback-press'
|
|
3
3
|
import { useEffect } from 'react'
|
|
4
|
-
import {
|
|
4
|
+
import { StyleSheet, useWindowDimensions } from 'react-native'
|
|
5
5
|
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'
|
|
6
6
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export const MATCH_CHIPS_DESKTOP_MIN_WIDTH = 768
|
|
8
|
+
import { useAtComputer } from '../hooks/useAtComputer'
|
|
9
|
+
|
|
11
10
|
// How long the chips take to fade out when play starts, and back in when it stops.
|
|
12
11
|
export const MATCH_CHIPS_FADE_MS = 150
|
|
13
12
|
// The chip's box: a 24 icon in IconButton's 48, with Paper's margin zeroed.
|
|
@@ -17,12 +16,12 @@ const EDGE_PAD = 8
|
|
|
17
16
|
export interface MatchChipsProps {
|
|
18
17
|
/** Whether play is on right now (a live puck, a shot in flight, a round being ridden). While it is,
|
|
19
18
|
* the chips fade out and can't be pressed, so they never cover play; they come back the moment it
|
|
20
|
-
* stops (between points, a round's card, Game Over). Ignored
|
|
19
|
+
* stops (between points, a round's card, Game Over). Ignored at a computer, where they stay up. */
|
|
21
20
|
playing: boolean
|
|
22
21
|
/** Two players at opposite ends of one screen right now (2 Player face to face). The chips then sit
|
|
23
22
|
* on the side edges at the line between the players, rather than in the top corners, which are the
|
|
24
|
-
* far player's end. False for one human (vs CPU, solo) and for side by side. Ignored
|
|
25
|
-
*
|
|
23
|
+
* far player's end. False for one human (vs CPU, solo) and for side by side. Ignored at a
|
|
24
|
+
* computer: both players share one upright screen there. */
|
|
26
25
|
faceToFace: boolean
|
|
27
26
|
/** Where that line between the players is, as a y in the window. Defaults to the window's middle;
|
|
28
27
|
* pass it when the board isn't centered (a band of score above it, say). */
|
|
@@ -34,13 +33,14 @@ export interface MatchChipsProps {
|
|
|
34
33
|
/** The back and settings chips over a match, in every game of every pack: one rule for where they sit
|
|
35
34
|
* and when they show. The window's top corners (the same spot as hud's CornerActionButtons on every
|
|
36
35
|
* other screen), except 2 Player face to face, which puts them on the side edges at the line between
|
|
37
|
-
* the players; faded out while play is on, except
|
|
36
|
+
* the players; faded out while play is on, except at a computer (useAtComputer: the web without a
|
|
37
|
+
* touch-first screen), where they stay up in the page's corners. */
|
|
38
38
|
export function MatchChips({ playing, faceToFace, centerLineY, onBack, onSettings }: MatchChipsProps) {
|
|
39
|
-
const {
|
|
39
|
+
const { height } = useWindowDimensions()
|
|
40
40
|
const insets = useSafeAreaInsets()
|
|
41
41
|
const { dark } = useAutoPaperTheme()
|
|
42
|
-
const
|
|
43
|
-
const visible =
|
|
42
|
+
const atComputer = useAtComputer()
|
|
43
|
+
const visible = atComputer || !playing
|
|
44
44
|
const opacity = useSharedValue(visible ? 1 : 0)
|
|
45
45
|
useEffect(() => {
|
|
46
46
|
opacity.value = withTiming(visible ? 1 : 0, { duration: MATCH_CHIPS_FADE_MS })
|
|
@@ -49,7 +49,7 @@ export function MatchChips({ playing, faceToFace, centerLineY, onBack, onSetting
|
|
|
49
49
|
|
|
50
50
|
const iconColor = dark ? '#FFFFFF' : '#000000'
|
|
51
51
|
const containerColor = dark ? 'rgba(0,0,0,0.55)' : 'rgba(255,255,255,0.55)'
|
|
52
|
-
const between = faceToFace && !
|
|
52
|
+
const between = faceToFace && !atComputer
|
|
53
53
|
const top = between ? (centerLineY ?? height / 2) - CHIP_SIZE / 2 : EDGE_PAD + insets.top
|
|
54
54
|
const backSlot = { top, left: EDGE_PAD + insets.left }
|
|
55
55
|
const settingsSlot = { top, right: EDGE_PAD + insets.right }
|