@tastic/shell 0.6.0 → 0.7.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 +8 -8
- package/dist/index.d.mts +9 -6
- package/dist/index.d.ts +9 -6
- package/dist/index.js +142 -126
- package/dist/index.mjs +115 -99
- package/package.json +8 -8
- package/src/components/MatchChips.tsx +12 -12
- package/src/components/OpponentPicker.tsx +112 -0
- 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 +11 -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,26 @@ 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";
|
|
586
561
|
function SettingsDialog({ visible, onDismiss, onShowHowToPlay, children }) {
|
|
587
562
|
const onUpdateError = useUpdateErrorToast2();
|
|
588
563
|
const { settings, setSettings } = useAppSettings();
|
|
589
564
|
const { otaVersion, SettingsSections, edgeGuard = true } = usePack();
|
|
590
565
|
const edgeGuardProps = edgeGuard ? { deferBottomEdgeGestures: settings.deferBottomEdgeGestures, onDeferBottomEdgeGestures: (value) => setSettings({ deferBottomEdgeGestures: value }) } : {};
|
|
591
|
-
return /* @__PURE__ */
|
|
566
|
+
return /* @__PURE__ */ jsxs3(BaseSettingsDialog, { visible, onDismiss, version: otaVersion, ...edgeGuardProps, onUpdateError, onShowHowToPlay, children: [
|
|
592
567
|
children,
|
|
593
568
|
SettingsSections && /* @__PURE__ */ jsx11(SettingsSections, {})
|
|
594
569
|
] });
|
|
595
570
|
}
|
|
596
571
|
|
|
572
|
+
// src/hooks/useAtComputer.ts
|
|
573
|
+
import { useIsTouchPrimaryDevice as useIsTouchPrimaryDevice2 } from "@tastic/core";
|
|
574
|
+
import { Platform as Platform3 } from "react-native";
|
|
575
|
+
function useAtComputer() {
|
|
576
|
+
const touchPrimary = useIsTouchPrimaryDevice2();
|
|
577
|
+
return Platform3.OS === "web" && !touchPrimary;
|
|
578
|
+
}
|
|
579
|
+
|
|
597
580
|
// src/hooks/useOpponent.ts
|
|
598
581
|
import { useCallback as useCallback7 } from "react";
|
|
599
582
|
import { useDispatch as useDispatch7, useSelector as useSelector6 } from "react-redux";
|
|
@@ -747,8 +730,10 @@ var liveplayActions = actions5;
|
|
|
747
730
|
var liveplaySlice_default = reducer5;
|
|
748
731
|
|
|
749
732
|
// src/screens/LoadoutScreen.tsx
|
|
750
|
-
import { jsx as jsx12, jsxs as
|
|
733
|
+
import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
751
734
|
var PANEL_SWAP_FADE_MS = 180;
|
|
735
|
+
var P1_CONTROL_ID = "p1-control";
|
|
736
|
+
var P2_CONTROL_ID = "p2-control";
|
|
752
737
|
function LoadoutScreen({ game }) {
|
|
753
738
|
const pack = usePack();
|
|
754
739
|
const { seatTwo: savedSeatTwo, setSeatTwo } = useOpponent();
|
|
@@ -761,7 +746,9 @@ function LoadoutScreen({ game }) {
|
|
|
761
746
|
const { openFor } = useGameGuide();
|
|
762
747
|
const { profiles, lastSelected, selectProfile } = useProfiles();
|
|
763
748
|
const { orientationMode } = useOrientationState();
|
|
764
|
-
const
|
|
749
|
+
const atComputer = useAtComputer();
|
|
750
|
+
const twoPlayerMode = game.sideBySideAtComputer && atComputer ? "sideBySide" : game.sideBySideWhenWide ? orientationMode : "faceToFace";
|
|
751
|
+
const panelMode = p2IsHuman ? twoPlayerMode : orientationMode;
|
|
765
752
|
const { panelLayout, panelFadeStyle } = useDualZoneLayout(panelMode, false, true, false, PANEL_SWAP_FADE_MS);
|
|
766
753
|
const playersRowFade = panelFadeStyle;
|
|
767
754
|
const [settingsOpen, setSettingsOpen] = useState4(false);
|
|
@@ -770,7 +757,7 @@ function LoadoutScreen({ game }) {
|
|
|
770
757
|
const { p1Color, p2Color, handleP1ColorChange, handleP2ColorChange } = useSeatColors(p2IsHuman);
|
|
771
758
|
const dispatch = useDispatch9();
|
|
772
759
|
const handleP1ProfileSelect = useCallback9((profile) => selectProfile(1, profile?.id ?? null), [selectProfile]);
|
|
773
|
-
const handleP2ProfileSelect = useCallback9((
|
|
760
|
+
const handleP2ProfileSelect = useCallback9((profile) => selectProfile(2, profile?.id ?? null), [selectProfile]);
|
|
774
761
|
const [ready, setReady] = useState4({ 1: false, 2: false });
|
|
775
762
|
useFocusEffect2(
|
|
776
763
|
useCallback9(() => {
|
|
@@ -801,16 +788,16 @@ function LoadoutScreen({ game }) {
|
|
|
801
788
|
const p1Drawn = seatPreview ? seatPreview(p1Color) : p1Color;
|
|
802
789
|
const p2Drawn = seatPreview ? seatPreview(p2Color) : p2Color;
|
|
803
790
|
const triggerStyle = useMemo5(() => pack.Backdrop ? [styles3.pill, { backgroundColor: themeColors.surface }] : void 0, [pack.Backdrop, themeColors.surface]);
|
|
791
|
+
const readySurface = pack.Backdrop ? themeColors.surface : void 0;
|
|
804
792
|
const isPortrait = panelLayout.orientationMode === "faceToFace";
|
|
805
793
|
const showMetaInSharedBand = needsSharedNeutralZone(panelLayout.orientationMode, humanSeats.length);
|
|
806
794
|
const p2ZoneStyle = p2IsHuman ? isPortrait ? styles3.p2ZoneTop : styles3.p2ZoneRight : null;
|
|
807
|
-
const showReadyButton = p2IsHuman;
|
|
808
795
|
const seatPickerAlign = p2IsHuman ? void 0 : pack.usePickerAlign;
|
|
809
796
|
const layoutKey = p2IsHuman ? "zones" : "row";
|
|
810
797
|
const controlsHost = usePopoverHost();
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
const
|
|
798
|
+
const p2Host = usePopoverHost();
|
|
799
|
+
const p1PickerOpen = controlsHost.openId?.startsWith("p1-") ?? false;
|
|
800
|
+
const p1ControlOpen = controlsHost.openId === P1_CONTROL_ID;
|
|
814
801
|
const optionsRow = loadout.options({
|
|
815
802
|
host: controlsHost,
|
|
816
803
|
accentColor: themeColors.tertiary,
|
|
@@ -825,11 +812,23 @@ function LoadoutScreen({ game }) {
|
|
|
825
812
|
const sharedControls = showMetaInSharedBand ? /* @__PURE__ */ jsx12(SharedActionBand, { onBack: safeBack, onSettings: () => setSettingsOpen(true), onRandomize: loadout.randomize, onReset: loadout.reset, fg, popoverOpen: optionsPopoverOpen, children: optionsRow }) : optionsRow;
|
|
826
813
|
const controlLook = { accentColor: themeColors.tertiary, mutedColor: fgMuted, dark };
|
|
827
814
|
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,
|
|
815
|
+
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}`);
|
|
816
|
+
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}`);
|
|
817
|
+
const toggleP1Ready = () => setReady((r) => ({ ...r, 1: !r[1] }));
|
|
818
|
+
const seatOne = /* @__PURE__ */ jsxs4(View3, { style: [styles3.seatOne, !p2IsHuman && styles3.seatOneAlone, p1PickerOpen && styles3.raised], children: [
|
|
819
|
+
/* @__PURE__ */ jsxs4(View3, { style: [styles3.seatOneButtons, p1PickerOpen && styles3.raised], children: [
|
|
820
|
+
/* @__PURE__ */ jsxs4(Animated.View, { style: [styles3.playersRow, p1PickerOpen && styles3.raised, !p2IsHuman && playersRowFade], children: [
|
|
821
|
+
p1Panel,
|
|
822
|
+
opponentPicker
|
|
823
|
+
] }),
|
|
824
|
+
p1Control && /* @__PURE__ */ jsx12(View3, { style: [styles3.controlRow, p1ControlOpen && styles3.raised], children: p1Control })
|
|
825
|
+
] }),
|
|
826
|
+
/* @__PURE__ */ jsx12(ReadyButton, { color: p1Drawn, ready: ready[1], onToggleReady: toggleP1Ready, surfaceColor: readySurface, dark, style: !p2IsHuman && p1PickerOpen && styles3.readyButtonHidden })
|
|
827
|
+
] });
|
|
829
828
|
const p2Control = p2IsHuman ? loadout.seatControl(2, p2Host, { ...controlLook, seatColor: p2Drawn }) : void 0;
|
|
830
|
-
const p2Panel =
|
|
831
|
-
return /* @__PURE__ */
|
|
832
|
-
/* @__PURE__ */
|
|
829
|
+
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");
|
|
830
|
+
return /* @__PURE__ */ jsxs4(View3, { style: [styles3.container, { backgroundColor: bg }], children: [
|
|
831
|
+
/* @__PURE__ */ jsxs4(View3, { style: styles3.frame, children: [
|
|
833
832
|
/* @__PURE__ */ jsx12(PressAwayOverlay, { active: controlsHost.openId !== null, onPress: controlsHost.close }),
|
|
834
833
|
p2ZoneStyle && /* @__PURE__ */ jsx12(PressAwayOverlay, { active: controlsHost.openId !== null || p2Host.openId !== null, onPress: p2Host.close, style: p2ZoneStyle }),
|
|
835
834
|
!showMetaInSharedBand && /* @__PURE__ */ jsx12(CornerActionButtons, { onBack: safeBack, onSettings: () => setSettingsOpen(true), fg, insets }),
|
|
@@ -838,20 +837,16 @@ function LoadoutScreen({ game }) {
|
|
|
838
837
|
{
|
|
839
838
|
panelLayout,
|
|
840
839
|
panelFadeStyle,
|
|
841
|
-
p1:
|
|
840
|
+
p1: seatOne,
|
|
842
841
|
p2: p2Panel,
|
|
843
842
|
shared: sharedControls,
|
|
844
|
-
p1Elevated:
|
|
843
|
+
p1Elevated: p1PickerOpen,
|
|
845
844
|
p2Elevated: p2Host.openId !== null,
|
|
846
845
|
sharedElevated: optionsPopoverOpen
|
|
847
846
|
}
|
|
848
|
-
) : /* @__PURE__ */
|
|
847
|
+
) : /* @__PURE__ */ jsxs4(View3, { style: styles3.stackedZone, children: [
|
|
849
848
|
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 })
|
|
849
|
+
seatOne
|
|
855
850
|
] })
|
|
856
851
|
] }),
|
|
857
852
|
/* @__PURE__ */ jsx12(SettingsDialog, { visible: settingsOpen, onDismiss: () => setSettingsOpen(false), onShowHowToPlay: () => openFor(game.id) })
|
|
@@ -865,6 +860,12 @@ var styles3 = StyleSheet3.create({
|
|
|
865
860
|
container: {
|
|
866
861
|
flex: 1
|
|
867
862
|
},
|
|
863
|
+
// Player 1's controls, under their row of buttons.
|
|
864
|
+
controlRow: {
|
|
865
|
+
alignItems: "center",
|
|
866
|
+
flexDirection: "row",
|
|
867
|
+
gap: 12
|
|
868
|
+
},
|
|
868
869
|
// A real full-bleed box to size the absolutely-positioned children against.
|
|
869
870
|
frame: {
|
|
870
871
|
alignItems: "center",
|
|
@@ -891,21 +892,37 @@ var styles3 = StyleSheet3.create({
|
|
|
891
892
|
pill: {
|
|
892
893
|
borderRadius: 999
|
|
893
894
|
},
|
|
894
|
-
//
|
|
895
|
-
//
|
|
895
|
+
// Seat 1's button and the opponent button read as one evenly spaced row of buttons, at
|
|
896
|
+
// PlayerSetupPanel's own pickerRow gap (12).
|
|
896
897
|
playersRow: {
|
|
897
898
|
flexDirection: "row",
|
|
898
899
|
gap: 12
|
|
899
900
|
},
|
|
900
|
-
|
|
901
|
+
// Lifts whichever piece of player 1's has a menu open above its later siblings (each View is its
|
|
902
|
+
// own stacking context on the web).
|
|
903
|
+
raised: {
|
|
901
904
|
zIndex: 100
|
|
902
905
|
},
|
|
903
|
-
// See
|
|
906
|
+
// See p1PickerOpen: opacity 0 (not display:'none') keeps the button's layout space, and
|
|
904
907
|
// pointerEvents:'none' keeps the sliver an open menu doesn't cover from being a hidden tap target.
|
|
905
908
|
readyButtonHidden: {
|
|
906
909
|
opacity: 0,
|
|
907
910
|
pointerEvents: "none"
|
|
908
911
|
},
|
|
912
|
+
// Player 1's column: their row of buttons and controls, then Ready, at PlayerSetupPanel's own
|
|
913
|
+
// rhythm in their half of 2 Player.
|
|
914
|
+
seatOne: {
|
|
915
|
+
alignItems: "center",
|
|
916
|
+
gap: 12
|
|
917
|
+
},
|
|
918
|
+
// With one person, Ready sits as far under the buttons as the match options sit above them.
|
|
919
|
+
seatOneAlone: {
|
|
920
|
+
gap: 28
|
|
921
|
+
},
|
|
922
|
+
seatOneButtons: {
|
|
923
|
+
alignItems: "center",
|
|
924
|
+
gap: 12
|
|
925
|
+
},
|
|
909
926
|
stackedZone: {
|
|
910
927
|
alignItems: "center",
|
|
911
928
|
gap: 28
|
|
@@ -1031,7 +1048,7 @@ function useHomeLayout() {
|
|
|
1031
1048
|
}
|
|
1032
1049
|
|
|
1033
1050
|
// src/screens/HomeScreen.tsx
|
|
1034
|
-
import { Fragment as Fragment4, jsx as jsx14, jsxs as
|
|
1051
|
+
import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1035
1052
|
function HomeScreen() {
|
|
1036
1053
|
const pack = usePack();
|
|
1037
1054
|
const { consumeResumeGame } = useLastActiveGame();
|
|
@@ -1099,19 +1116,19 @@ function HomeMenu() {
|
|
|
1099
1116
|
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
1117
|
const pressAway = /* @__PURE__ */ jsx14(PressAwayOverlay2, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles4.pressAway });
|
|
1101
1118
|
const RulesSheet = rulesGame?.Rules;
|
|
1102
|
-
return /* @__PURE__ */
|
|
1103
|
-
/* @__PURE__ */ jsx14(View4, { style: styles4.frame, children: /* @__PURE__ */
|
|
1104
|
-
isWide ? /* @__PURE__ */
|
|
1119
|
+
return /* @__PURE__ */ jsxs5(View4, { style: [styles4.container, { backgroundColor: bg }], children: [
|
|
1120
|
+
/* @__PURE__ */ jsx14(View4, { style: styles4.frame, children: /* @__PURE__ */ jsxs5(RulesHost, { enabled: pack.games.some((game) => game.Rules), children: [
|
|
1121
|
+
isWide ? /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
1105
1122
|
pressAway,
|
|
1106
|
-
/* @__PURE__ */
|
|
1123
|
+
/* @__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
1124
|
tabBar && /* @__PURE__ */ jsx14(View4, { style: [styles4.wideTabs, { width: layout.rightPaneWidth }], children: tabBar }),
|
|
1108
1125
|
/* @__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
1126
|
] }),
|
|
1110
|
-
/* @__PURE__ */
|
|
1127
|
+
/* @__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
1128
|
/* @__PURE__ */ jsx14(pack.HeroTitle, { words: pack.wordmark, color: fg, p1Color, p2Color, active: heroActive, fit: layout.heroFit }),
|
|
1112
1129
|
/* @__PURE__ */ jsx14(View4, { style: styles4.widePicker, children: picker })
|
|
1113
1130
|
] })
|
|
1114
|
-
] }) : /* @__PURE__ */
|
|
1131
|
+
] }) : /* @__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
1132
|
pressAway,
|
|
1116
1133
|
/* @__PURE__ */ jsx14(pack.HeroTitle, { words: pack.wordmark, color: fg, p1Color, p2Color, active: heroActive }),
|
|
1117
1134
|
/* @__PURE__ */ jsx14(View4, { style: [styles4.picker, pickerHost.openId !== null && styles4.pickerOpen], children: picker }),
|
|
@@ -1167,10 +1184,10 @@ function GameRow({ game, hidden, dark, p1Color, p2Color, previewRevealed, onPres
|
|
|
1167
1184
|
return (
|
|
1168
1185
|
// The rules button sits beside the row's own button, laid over its end, not inside it: a button
|
|
1169
1186
|
// inside a button is invalid on the web.
|
|
1170
|
-
/* @__PURE__ */
|
|
1171
|
-
/* @__PURE__ */
|
|
1187
|
+
/* @__PURE__ */ jsxs5(View4, { style: hidden ? styles4.hidden : void 0, children: [
|
|
1188
|
+
/* @__PURE__ */ jsxs5(Pressable, { onPress, style: [styles4.row, { backgroundColor: well.bg }], accessibilityRole: "button", accessibilityLabel: game.title, children: [
|
|
1172
1189
|
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__ */
|
|
1190
|
+
/* @__PURE__ */ jsxs5(View4, { style: styles4.rowText, children: [
|
|
1174
1191
|
/* @__PURE__ */ jsx14(Text, { variant: "titleLarge", style: [styles4.rowTitle, { color: well.fg }], numberOfLines: 1, children: game.title }),
|
|
1175
1192
|
/* @__PURE__ */ jsx14(Text, { variant: "bodyMedium", style: { color: well.fgMuted }, numberOfLines: 2, children: game.blurb })
|
|
1176
1193
|
] })
|
|
@@ -1181,8 +1198,8 @@ function GameRow({ game, hidden, dark, p1Color, p2Color, previewRevealed, onPres
|
|
|
1181
1198
|
}
|
|
1182
1199
|
function GameTile({ game, hidden, width, previewWidth, dark, p1Color, p2Color, previewRevealed, onPress, onRules }) {
|
|
1183
1200
|
const well = useWellColors();
|
|
1184
|
-
return /* @__PURE__ */
|
|
1185
|
-
/* @__PURE__ */
|
|
1201
|
+
return /* @__PURE__ */ jsxs5(View4, { style: [{ width }, hidden && styles4.hidden], children: [
|
|
1202
|
+
/* @__PURE__ */ jsxs5(Pressable, { onPress, style: [styles4.tile, { backgroundColor: well.bg }], accessibilityRole: "button", accessibilityLabel: game.title, children: [
|
|
1186
1203
|
/* @__PURE__ */ jsx14(PreviewBox, { game, width: previewWidth, revealed: previewRevealed, dark, p1Color, p2Color }),
|
|
1187
1204
|
/* @__PURE__ */ jsx14(Text, { variant: "titleMedium", style: [styles4.rowTitle, { color: well.fg }], numberOfLines: 1, children: game.title }),
|
|
1188
1205
|
/* @__PURE__ */ jsx14(Text, { variant: "bodySmall", style: { color: well.fgMuted }, numberOfLines: 3, children: game.blurb })
|
|
@@ -1368,7 +1385,7 @@ import { StyleSheet as StyleSheet5, View as View5 } from "react-native";
|
|
|
1368
1385
|
import { SegmentedButtons as SegmentedButtons2, Text as Text2 } from "react-native-paper";
|
|
1369
1386
|
import { useSafeAreaInsets as useSafeAreaInsets3 } from "react-native-safe-area-context";
|
|
1370
1387
|
import { useDispatch as useDispatch10 } from "react-redux";
|
|
1371
|
-
import { jsx as jsx16, jsxs as
|
|
1388
|
+
import { jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1372
1389
|
var STATS = "stats";
|
|
1373
1390
|
var ACHIEVEMENTS = "achievements";
|
|
1374
1391
|
var TABS = [
|
|
@@ -1421,10 +1438,10 @@ function StatsContent({ pickerHost }) {
|
|
|
1421
1438
|
const surface = pack.Backdrop ? { backgroundColor: colors.surface } : void 0;
|
|
1422
1439
|
const triggerStyle = pack.Backdrop ? [styles5.pill, surface] : void 0;
|
|
1423
1440
|
const PackTabContent = packTab?.Content;
|
|
1424
|
-
return /* @__PURE__ */
|
|
1441
|
+
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
1442
|
/* @__PURE__ */ jsx16(PressAwayOverlay3, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles5.pressAway }),
|
|
1426
1443
|
/* @__PURE__ */ jsx16(SegmentedButtons2, { value: tab, onValueChange: setTab, buttons: tabButtons, style: [styles5.tabs, surface] }),
|
|
1427
|
-
/* @__PURE__ */
|
|
1444
|
+
/* @__PURE__ */ jsxs6(View5, { style: [styles5.filters, pickerHost.openId !== null && styles5.filtersOpen], onStartShouldSetResponder: () => pickerHost.openId !== null, children: [
|
|
1428
1445
|
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
1446
|
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
1447
|
] }),
|
|
@@ -1445,7 +1462,7 @@ function GameStats({ game, tab, profileId, showTitle, titleColor }) {
|
|
|
1445
1462
|
const { StatsSections, AchievementList } = game;
|
|
1446
1463
|
const Sections = tab === STATS ? StatsSections : AchievementList;
|
|
1447
1464
|
if (!Sections) return null;
|
|
1448
|
-
return /* @__PURE__ */
|
|
1465
|
+
return /* @__PURE__ */ jsxs6(View5, { style: styles5.game, children: [
|
|
1449
1466
|
showTitle && /* @__PURE__ */ jsx16(Text2, { variant: "titleLarge", style: [styles5.gameTitle, { color: titleColor }], children: game.title }),
|
|
1450
1467
|
/* @__PURE__ */ jsx16(Sections, { profileId })
|
|
1451
1468
|
] });
|
|
@@ -1496,20 +1513,19 @@ function imageGameIcon(images) {
|
|
|
1496
1513
|
import { useAutoPaperTheme as useAutoPaperTheme6 } from "@rific/auto-paper";
|
|
1497
1514
|
import { IconButton as IconButton2 } from "@rific/feedback-press";
|
|
1498
1515
|
import { useEffect as useEffect7 } from "react";
|
|
1499
|
-
import {
|
|
1516
|
+
import { StyleSheet as StyleSheet6, useWindowDimensions as useWindowDimensions2 } from "react-native";
|
|
1500
1517
|
import Animated2, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
|
|
1501
1518
|
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;
|
|
1519
|
+
import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1504
1520
|
var MATCH_CHIPS_FADE_MS = 150;
|
|
1505
1521
|
var CHIP_SIZE = 48;
|
|
1506
1522
|
var EDGE_PAD = 8;
|
|
1507
1523
|
function MatchChips({ playing, faceToFace, centerLineY, onBack, onSettings }) {
|
|
1508
|
-
const {
|
|
1524
|
+
const { height } = useWindowDimensions2();
|
|
1509
1525
|
const insets = useSafeAreaInsets4();
|
|
1510
1526
|
const { dark } = useAutoPaperTheme6();
|
|
1511
|
-
const
|
|
1512
|
-
const visible =
|
|
1527
|
+
const atComputer = useAtComputer();
|
|
1528
|
+
const visible = atComputer || !playing;
|
|
1513
1529
|
const opacity = useSharedValue(visible ? 1 : 0);
|
|
1514
1530
|
useEffect7(() => {
|
|
1515
1531
|
opacity.value = withTiming(visible ? 1 : 0, { duration: MATCH_CHIPS_FADE_MS });
|
|
@@ -1517,12 +1533,12 @@ function MatchChips({ playing, faceToFace, centerLineY, onBack, onSettings }) {
|
|
|
1517
1533
|
const fadeStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
|
|
1518
1534
|
const iconColor = dark ? "#FFFFFF" : "#000000";
|
|
1519
1535
|
const containerColor = dark ? "rgba(0,0,0,0.55)" : "rgba(255,255,255,0.55)";
|
|
1520
|
-
const between = faceToFace && !
|
|
1536
|
+
const between = faceToFace && !atComputer;
|
|
1521
1537
|
const top = between ? (centerLineY ?? height / 2) - CHIP_SIZE / 2 : EDGE_PAD + insets.top;
|
|
1522
1538
|
const backSlot = { top, left: EDGE_PAD + insets.left };
|
|
1523
1539
|
const settingsSlot = { top, right: EDGE_PAD + insets.right };
|
|
1524
1540
|
const pointerEvents = visible ? "box-none" : "none";
|
|
1525
|
-
return /* @__PURE__ */
|
|
1541
|
+
return /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
1526
1542
|
/* @__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
1543
|
/* @__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
1544
|
] });
|
|
@@ -1787,7 +1803,6 @@ export {
|
|
|
1787
1803
|
GUEST_PLAYER_ID,
|
|
1788
1804
|
HomeScreen,
|
|
1789
1805
|
LoadoutRoute,
|
|
1790
|
-
MATCH_CHIPS_DESKTOP_MIN_WIDTH,
|
|
1791
1806
|
MATCH_CHIPS_FADE_MS,
|
|
1792
1807
|
MatchChips,
|
|
1793
1808
|
PREVIEW_ASPECT,
|
|
@@ -1825,6 +1840,7 @@ export {
|
|
|
1825
1840
|
seatTwoFor,
|
|
1826
1841
|
useActiveRound,
|
|
1827
1842
|
useAppSettings,
|
|
1843
|
+
useAtComputer,
|
|
1828
1844
|
useGameGuide,
|
|
1829
1845
|
useOpponent,
|
|
1830
1846
|
usePack,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|
|
@@ -75,7 +75,7 @@
|
|
|
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.28.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.28.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 }
|