@dreamboard-games/sdk 0.2.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/LICENSE.md +96 -0
- package/README.md +12 -0
- package/dist/HandView-ncJIVLhN.d.ts +193 -0
- package/dist/ResourceCounter-CTREyF73.d.ts +102 -0
- package/dist/ThemeProvider-fy0_QzgO.d.ts +99 -0
- package/dist/bundle-TIZcw8LB.d.ts +281 -0
- package/dist/cards-Sl3b40Mv.d.ts +13 -0
- package/dist/chunk-7YAHLYBR.js +481 -0
- package/dist/chunk-7YAHLYBR.js.map +1 -0
- package/dist/chunk-FDNZTDD6.js +8085 -0
- package/dist/chunk-FDNZTDD6.js.map +1 -0
- package/dist/chunk-GKKBPPSW.js +598 -0
- package/dist/chunk-GKKBPPSW.js.map +1 -0
- package/dist/chunk-I46YJSOD.js +1 -0
- package/dist/chunk-I46YJSOD.js.map +1 -0
- package/dist/chunk-KAELH4KC.js +104 -0
- package/dist/chunk-KAELH4KC.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-T3ZKNUZ7.js +1 -0
- package/dist/chunk-T3ZKNUZ7.js.map +1 -0
- package/dist/chunk-T52J5RMF.js +1 -0
- package/dist/chunk-T52J5RMF.js.map +1 -0
- package/dist/chunk-TDSWKVZ4.js +5401 -0
- package/dist/chunk-TDSWKVZ4.js.map +1 -0
- package/dist/chunk-U5C6BONG.js +34 -0
- package/dist/chunk-U5C6BONG.js.map +1 -0
- package/dist/chunk-VDXOF4FW.js +69 -0
- package/dist/chunk-VDXOF4FW.js.map +1 -0
- package/dist/chunk-VFTAA4WO.js +115 -0
- package/dist/chunk-VFTAA4WO.js.map +1 -0
- package/dist/chunk-WN74KVNY.js +17 -0
- package/dist/chunk-WN74KVNY.js.map +1 -0
- package/dist/chunk-WYPQ3GG5.js +10990 -0
- package/dist/chunk-WYPQ3GG5.js.map +1 -0
- package/dist/components-D5ZRE2Hl.d.ts +1451 -0
- package/dist/generated/runtime/primitives.d.ts +12 -0
- package/dist/generated/runtime/primitives.js +180 -0
- package/dist/generated/runtime/primitives.js.map +1 -0
- package/dist/generated/runtime-api.d.ts +3 -0
- package/dist/generated/runtime-api.js +2 -0
- package/dist/generated/runtime-api.js.map +1 -0
- package/dist/generated/runtime.d.ts +14 -0
- package/dist/generated/runtime.js +18 -0
- package/dist/generated/runtime.js.map +1 -0
- package/dist/generated/workspace-contract.d.ts +14 -0
- package/dist/generated/workspace-contract.js +14 -0
- package/dist/generated/workspace-contract.js.map +1 -0
- package/dist/hex-board-view-D_07hO6O.d.ts +933 -0
- package/dist/hex-color-MhOyuY-o.d.ts +8 -0
- package/dist/index-BwqPQtBu.d.ts +1433 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/reducer-bundle-abi.d.ts +1083 -0
- package/dist/infrastructure/reducer-bundle-abi.js +14 -0
- package/dist/infrastructure/reducer-bundle-abi.js.map +1 -0
- package/dist/infrastructure/workspace-codegen.d.ts +53 -0
- package/dist/infrastructure/workspace-codegen.js +44 -0
- package/dist/infrastructure/workspace-codegen.js.map +1 -0
- package/dist/manifest-contract-BNHVGFtU.d.ts +9 -0
- package/dist/package-set.d.ts +13 -0
- package/dist/package-set.js +12 -0
- package/dist/package-set.js.map +1 -0
- package/dist/primitive-props-DpKs-GCr.d.ts +11 -0
- package/dist/reducer.d.ts +3786 -0
- package/dist/reducer.js +8131 -0
- package/dist/reducer.js.map +1 -0
- package/dist/runtime/primitives.d.ts +226 -0
- package/dist/runtime/primitives.js +180 -0
- package/dist/runtime/primitives.js.map +1 -0
- package/dist/runtime/types/runtime-api.d.ts +1 -0
- package/dist/runtime/types/runtime-api.js +2 -0
- package/dist/runtime/types/runtime-api.js.map +1 -0
- package/dist/runtime/workspace-contract.d.ts +172 -0
- package/dist/runtime/workspace-contract.js +14 -0
- package/dist/runtime/workspace-contract.js.map +1 -0
- package/dist/runtime-api-3dshj6kK.d.ts +101 -0
- package/dist/runtime-api-DWxvTr-O.d.ts +379 -0
- package/dist/runtime.d.ts +58 -0
- package/dist/runtime.js +13 -0
- package/dist/runtime.js.map +1 -0
- package/dist/slots-1GPGihk8.d.ts +8 -0
- package/dist/testing.d.ts +149 -0
- package/dist/testing.js +513 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +496 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/components.d.ts +16 -0
- package/dist/ui/components.js +192 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/defaults.d.ts +19 -0
- package/dist/ui/defaults.js +104 -0
- package/dist/ui/defaults.js.map +1 -0
- package/dist/ui/plugin-styles.css +250 -0
- package/dist/ui/types/player-state.d.ts +365 -0
- package/dist/ui/types/player-state.js +1 -0
- package/dist/ui/types/player-state.js.map +1 -0
- package/dist/ui-contract-iQfTtUSL.d.ts +1161 -0
- package/dist/ui.d.ts +320 -0
- package/dist/ui.js +253 -0
- package/dist/ui.js.map +1 -0
- package/package.json +199 -0
- package/src/generated/reducer-contract/builders.ts +90 -0
- package/src/generated/reducer-contract/version.ts +9 -0
- package/src/generated/reducer-contract/wire.ts +100 -0
- package/src/generated/reducer-contract/zod.ts +101 -0
- package/src/generated/runtime/primitives.ts +2 -0
- package/src/generated/runtime-api.ts +5 -0
- package/src/generated/runtime.ts +35 -0
- package/src/generated/workspace-contract.ts +2 -0
- package/src/index.ts +7 -0
- package/src/infrastructure/reducer-bundle-abi.ts +8 -0
- package/src/infrastructure/reducer-contract/bundle.ts +37 -0
- package/src/infrastructure/workspace-codegen/hex-geometry.ts +69 -0
- package/src/infrastructure/workspace-codegen/index.ts +64 -0
- package/src/infrastructure/workspace-codegen/manifest-contract.ts +6632 -0
- package/src/infrastructure/workspace-codegen/manifest-validation.ts +795 -0
- package/src/infrastructure/workspace-codegen/ownership.ts +131 -0
- package/src/infrastructure/workspace-codegen/preset-card-sets.ts +169 -0
- package/src/infrastructure/workspace-codegen/seeds.ts +1705 -0
- package/src/infrastructure/workspace-codegen.ts +1 -0
- package/src/package-set.ts +19 -0
- package/src/reducer/authoring/contract.ts +157 -0
- package/src/reducer/authoring/effect.ts +224 -0
- package/src/reducer/authoring/game.ts +23 -0
- package/src/reducer/authoring/interaction.ts +98 -0
- package/src/reducer/authoring/phase.ts +300 -0
- package/src/reducer/authoring/types.ts +70 -0
- package/src/reducer/authoring/validation.ts +382 -0
- package/src/reducer/authoring/view-stage.ts +68 -0
- package/src/reducer/authoring.ts +29 -0
- package/src/reducer/bundle/ingress-bundle.ts +491 -0
- package/src/reducer/bundle/trusted/engine-instruction-resolver.ts +254 -0
- package/src/reducer/bundle/trusted/flow-instruction-resolver.ts +73 -0
- package/src/reducer/bundle/trusted/instruction-runner.ts +414 -0
- package/src/reducer/bundle/trusted/interaction-authorization.ts +137 -0
- package/src/reducer/bundle/trusted/interaction-collectors.ts +859 -0
- package/src/reducer/bundle/trusted/interaction-decision.ts +747 -0
- package/src/reducer/bundle/trusted/interaction-resolver.ts +95 -0
- package/src/reducer/bundle/trusted/interaction-types.ts +171 -0
- package/src/reducer/bundle/trusted/lifecycle-runner.ts +427 -0
- package/src/reducer/bundle/trusted/projection-builder.ts +356 -0
- package/src/reducer/bundle/trusted/projection-context.ts +39 -0
- package/src/reducer/bundle/trusted/rng-sampler.ts +150 -0
- package/src/reducer/bundle/trusted/runtime-registry.ts +120 -0
- package/src/reducer/bundle/trusted/runtime-scope.ts +336 -0
- package/src/reducer/bundle/trusted/simultaneous-player.ts +97 -0
- package/src/reducer/bundle/trusted/stage-resolver.ts +87 -0
- package/src/reducer/bundle/trusted/static-projection.ts +116 -0
- package/src/reducer/bundle/trusted/trusted-runtime-args.ts +97 -0
- package/src/reducer/bundle/trusted/trusted-runtime-result.ts +39 -0
- package/src/reducer/bundle/trusted/trusted-setup-profiles.ts +43 -0
- package/src/reducer/bundle/trusted/trusted-state-codec.ts +48 -0
- package/src/reducer/bundle/trusted-bundle.ts +97 -0
- package/src/reducer/bundle/types.ts +171 -0
- package/src/reducer/bundle.ts +2 -0
- package/src/reducer/client-param-schemas.ts +57 -0
- package/src/reducer/compose.ts +34 -0
- package/src/reducer/core/runtime-input.ts +30 -0
- package/src/reducer/core/runtime-instruction.ts +59 -0
- package/src/reducer/core/types.ts +62 -0
- package/src/reducer/definition-index.ts +277 -0
- package/src/reducer/derived.ts +106 -0
- package/src/reducer/effects.ts +92 -0
- package/src/reducer/engine/runtime-instruction-engine.ts +155 -0
- package/src/reducer/ingress/decode-runtime-input.ts +7 -0
- package/src/reducer/ingress/decode-session-state.ts +9 -0
- package/src/reducer/ingress/encode-session-state.ts +6 -0
- package/src/reducer/ingress/input-codec.ts +18 -0
- package/src/reducer/ingress/phase-schemas.ts +62 -0
- package/src/reducer/ingress/raw-types.ts +107 -0
- package/src/reducer/ingress/runtime-codec.ts +14 -0
- package/src/reducer/ingress/runtime-payload.ts +13 -0
- package/src/reducer/ingress/session-codec.ts +392 -0
- package/src/reducer/ingress/types.ts +6 -0
- package/src/reducer/inputs/boardInput.ts +217 -0
- package/src/reducer/inputs/boardTarget.ts +190 -0
- package/src/reducer/inputs/cardInput.ts +86 -0
- package/src/reducer/inputs/cardTarget.ts +101 -0
- package/src/reducer/inputs/choiceTarget.ts +104 -0
- package/src/reducer/inputs/defineInputs.ts +71 -0
- package/src/reducer/inputs/formInput.ts +809 -0
- package/src/reducer/inputs/many.ts +120 -0
- package/src/reducer/inputs/promptInput.ts +87 -0
- package/src/reducer/inputs/rngInput.ts +58 -0
- package/src/reducer/inputs/targetRule.ts +123 -0
- package/src/reducer/inputs.ts +41 -0
- package/src/reducer/model/definition.ts +1072 -0
- package/src/reducer/model/extract.ts +745 -0
- package/src/reducer/model/manifest.ts +570 -0
- package/src/reducer/model/queries.ts +641 -0
- package/src/reducer/model/runtime.ts +264 -0
- package/src/reducer/model/spec.ts +1386 -0
- package/src/reducer/model/table.ts +260 -0
- package/src/reducer/model.ts +7 -0
- package/src/reducer/ops.ts +1034 -0
- package/src/reducer/parse-utils.ts +28 -0
- package/src/reducer/per-player.ts +422 -0
- package/src/reducer/rng.ts +69 -0
- package/src/reducer/schema-helpers.ts +185 -0
- package/src/reducer/setup-bootstrap-helpers.ts +171 -0
- package/src/reducer/setup-bootstrap.ts +481 -0
- package/src/reducer/table-ops.ts +2671 -0
- package/src/reducer/table-queries.ts +372 -0
- package/src/reducer/transaction.ts +120 -0
- package/src/reducer.ts +314 -0
- package/src/runtime/primitives.ts +1 -0
- package/src/runtime/types/runtime-api.ts +1 -0
- package/src/runtime/workspace-contract.ts +32 -0
- package/src/runtime-internal/components/InteractionForm.tsx +1309 -0
- package/src/runtime-internal/components/PluginRuntime.tsx +103 -0
- package/src/runtime-internal/components/board/target-layer.ts +70 -0
- package/src/runtime-internal/context/ClientParamSchemaContext.tsx +44 -0
- package/src/runtime-internal/context/InteractionDraftContext.tsx +279 -0
- package/src/runtime-internal/context/PluginSessionContext.tsx +47 -0
- package/src/runtime-internal/context/PluginStateContext.tsx +262 -0
- package/src/runtime-internal/context/RuntimeContext.tsx +96 -0
- package/src/runtime-internal/defaults/components.tsx +409 -0
- package/src/runtime-internal/defaults/index.ts +11 -0
- package/src/runtime-internal/errors/ValidationError.ts +29 -0
- package/src/runtime-internal/hooks/useActivePlayers.ts +33 -0
- package/src/runtime-internal/hooks/useBoardInteractions.ts +665 -0
- package/src/runtime-internal/hooks/useGameSelector.ts +105 -0
- package/src/runtime-internal/hooks/useGameView.ts +9 -0
- package/src/runtime-internal/hooks/useInteractionByKey.ts +354 -0
- package/src/runtime-internal/hooks/useInteractionHandle.ts +438 -0
- package/src/runtime-internal/hooks/useIsMyTurn.ts +20 -0
- package/src/runtime-internal/hooks/useLobby.ts +76 -0
- package/src/runtime-internal/hooks/useMe.ts +48 -0
- package/src/runtime-internal/hooks/usePlayerInfo.ts +28 -0
- package/src/runtime-internal/hooks/usePlayerTurnOrder.ts +23 -0
- package/src/runtime-internal/hooks/usePluginRuntime.ts +147 -0
- package/src/runtime-internal/hooks/useSeatInbox.ts +61 -0
- package/src/runtime-internal/hooks/useSimultaneousPhase.ts +10 -0
- package/src/runtime-internal/index.ts +42 -0
- package/src/runtime-internal/internal.ts +43 -0
- package/src/runtime-internal/plugin-styles.css +250 -0
- package/src/runtime-internal/primitives/board.tsx +459 -0
- package/src/runtime-internal/primitives/dialog-lifecycle.ts +58 -0
- package/src/runtime-internal/primitives/dice.tsx +79 -0
- package/src/runtime-internal/primitives/game-ui-provider.tsx +35 -0
- package/src/runtime-internal/primitives/game.tsx +387 -0
- package/src/runtime-internal/primitives/hand-intent-adapter.ts +147 -0
- package/src/runtime-internal/primitives/hand-surface.tsx +594 -0
- package/src/runtime-internal/primitives/index.ts +196 -0
- package/src/runtime-internal/primitives/interaction-form-binding.tsx +56 -0
- package/src/runtime-internal/primitives/interaction-submit.ts +90 -0
- package/src/runtime-internal/primitives/interaction.tsx +987 -0
- package/src/runtime-internal/primitives/phase.tsx +43 -0
- package/src/runtime-internal/primitives/player-roster.tsx +302 -0
- package/src/runtime-internal/primitives/primitive-props.tsx +101 -0
- package/src/runtime-internal/primitives/prompt.tsx +255 -0
- package/src/runtime-internal/primitives/ui.tsx +60 -0
- package/src/runtime-internal/primitives/zone.tsx +791 -0
- package/src/runtime-internal/reducer.ts +30 -0
- package/src/runtime-internal/runtime/createPluginRuntimeAPI.ts +605 -0
- package/src/runtime-internal/types/plugin-state.ts +508 -0
- package/src/runtime-internal/types/reducer-state.ts +24 -0
- package/src/runtime-internal/types/runtime-api.ts +114 -0
- package/src/runtime-internal/ui-contract.ts +519 -0
- package/src/runtime-internal/utils/card-intent-adapter.ts +546 -0
- package/src/runtime-internal/utils/interaction-inputs.ts +492 -0
- package/src/runtime-internal/utils/interaction-labels.ts +23 -0
- package/src/runtime-internal/utils/interaction-router.ts +273 -0
- package/src/runtime-internal/utils/interaction-status.ts +74 -0
- package/src/runtime-internal/workspace-contract.ts +1170 -0
- package/src/runtime.ts +34 -0
- package/src/testing/create-expect-api.ts +352 -0
- package/src/testing/create-test-runtime.ts +381 -0
- package/src/testing/definitions.ts +127 -0
- package/src/testing/index.ts +3 -0
- package/src/testing.ts +1 -0
- package/src/type-stubs/manifest-contract.d.ts +42 -0
- package/src/type-stubs/manifest-contract.js +72 -0
- package/src/type-stubs/ui-contract.d.ts +5 -0
- package/src/type-stubs/ui-contract.js +1 -0
- package/src/types/authoring-card-properties.type-test.ts +266 -0
- package/src/types/authoring.ts +1282 -0
- package/src/types/cards.ts +19 -0
- package/src/types/contracts.ts +1550 -0
- package/src/types/generated-helpers.ts +35 -0
- package/src/types/index.ts +147 -0
- package/src/types/slots.ts +11 -0
- package/src/types.ts +1 -0
- package/src/ui/components/ActionButton.tsx +97 -0
- package/src/ui/components/ActionPanel.tsx +315 -0
- package/src/ui/components/Card.tsx +378 -0
- package/src/ui/components/CardDragSurface.tsx +1076 -0
- package/src/ui/components/ChromeSuppressionContext.tsx +70 -0
- package/src/ui/components/CostDisplay.tsx +145 -0
- package/src/ui/components/DiceRoller.tsx +581 -0
- package/src/ui/components/Drawer.tsx +180 -0
- package/src/ui/components/ErrorBoundary.tsx +275 -0
- package/src/ui/components/GameEndDisplay.tsx +398 -0
- package/src/ui/components/GameSkeleton.tsx +260 -0
- package/src/ui/components/Hand.tsx +468 -0
- package/src/ui/components/HandDock.tsx +299 -0
- package/src/ui/components/HandView.tsx +441 -0
- package/src/ui/components/MobileHandTray.tsx +381 -0
- package/src/ui/components/MoreActions.tsx +143 -0
- package/src/ui/components/PhaseIndicator.tsx +341 -0
- package/src/ui/components/PlayArea.tsx +146 -0
- package/src/ui/components/PrimaryActionButton.tsx +336 -0
- package/src/ui/components/PrimaryButton.tsx +45 -0
- package/src/ui/components/ResourceCounter.tsx +270 -0
- package/src/ui/components/StagingZone.tsx +134 -0
- package/src/ui/components/ThemedButton.tsx +113 -0
- package/src/ui/components/Toast.tsx +264 -0
- package/src/ui/components/board/HexGrid.tsx +1294 -0
- package/src/ui/components/board/NetworkGraph.tsx +476 -0
- package/src/ui/components/board/SlotSystem.tsx +388 -0
- package/src/ui/components/board/SquareGrid.tsx +1165 -0
- package/src/ui/components/board/TrackBoard.tsx +496 -0
- package/src/ui/components/board/ZoneMap.tsx +448 -0
- package/src/ui/components/board/hex-board-view.ts +123 -0
- package/src/ui/components/board/index.ts +142 -0
- package/src/ui/components/board/interaction-accessibility.ts +21 -0
- package/src/ui/components/board/target-layer.ts +66 -0
- package/src/ui/components/card-render-content.type-test.ts +27 -0
- package/src/ui/components/hand-layout-math.ts +163 -0
- package/src/ui/components/hand-pointer-engine.ts +413 -0
- package/src/ui/components/index.ts +245 -0
- package/src/ui/components.ts +1 -0
- package/src/ui/defaults/components.tsx +106 -0
- package/src/ui/defaults/index.ts +8 -0
- package/src/ui/defaults.ts +1 -0
- package/src/ui/errors/ValidationError.ts +29 -0
- package/src/ui/helpers/cards.ts +19 -0
- package/src/ui/helpers/track-board.ts +211 -0
- package/src/ui/hooks/useBoardTopology.ts +316 -0
- package/src/ui/hooks/useCards.ts +10 -0
- package/src/ui/hooks/useHandCardPointer.ts +381 -0
- package/src/ui/hooks/useHandLayout.ts +378 -0
- package/src/ui/hooks/useHandPresentation.ts +121 -0
- package/src/ui/hooks/useHexBoard.ts +74 -0
- package/src/ui/hooks/useHexGrid.ts +185 -0
- package/src/ui/hooks/useIsMobile.ts +35 -0
- package/src/ui/hooks/usePanZoom.ts +278 -0
- package/src/ui/hooks/useSquareBoard.ts +124 -0
- package/src/ui/hooks/useSquareGrid.ts +328 -0
- package/src/ui/index.ts +98 -0
- package/src/ui/internal/ui/alert.tsx +51 -0
- package/src/ui/internal/ui/button.tsx +58 -0
- package/src/ui/internal/ui/dialog.tsx +134 -0
- package/src/ui/internal/ui/input.tsx +21 -0
- package/src/ui/internal/ui/label.tsx +21 -0
- package/src/ui/internal/ui/select.tsx +129 -0
- package/src/ui/internal/ui/tooltip.tsx +54 -0
- package/src/ui/internal/ui/utils.ts +5 -0
- package/src/ui/plugin-styles.css +250 -0
- package/src/ui/primitives/dialog-lifecycle.ts +58 -0
- package/src/ui/primitives/dice.tsx +79 -0
- package/src/ui/primitives/primitive-props.tsx +101 -0
- package/src/ui/theme/ThemeProvider.tsx +252 -0
- package/src/ui/theme/board.ts +61 -0
- package/src/ui/theme/css-vars.ts +105 -0
- package/src/ui/theme/derive.ts +240 -0
- package/src/ui/theme/index.ts +61 -0
- package/src/ui/theme/presets/arcade.ts +261 -0
- package/src/ui/theme/presets/studio.ts +261 -0
- package/src/ui/theme/presets/tabletop.ts +266 -0
- package/src/ui/theme/tokens.ts +392 -0
- package/src/ui/types/hex-color.ts +20 -0
- package/src/ui/types/player-state.ts +463 -0
- package/src/ui/types/tiled-board.ts +785 -0
- package/src/ui/types/visual-state.ts +137 -0
- package/src/ui.ts +1 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useRef, useSyncExternalStore } from "react";
|
|
2
|
+
import { useRuntimeContext } from "../context/RuntimeContext.js";
|
|
3
|
+
import type { GameView } from "#dreamboard/ui-contract";
|
|
4
|
+
import type { PluginStateSnapshot } from "../types/reducer-state.js";
|
|
5
|
+
import type { PluginRuntimeAPI } from "../runtime/createPluginRuntimeAPI.js";
|
|
6
|
+
|
|
7
|
+
type EqualityFn<T> = (left: T, right: T) => boolean;
|
|
8
|
+
|
|
9
|
+
interface SelectorCache<T> {
|
|
10
|
+
rawView: GameView;
|
|
11
|
+
selected: T;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function defaultEquality<T>(left: T, right: T): boolean {
|
|
15
|
+
return Object.is(left, right);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function requireProjectedView(
|
|
19
|
+
snapshot: PluginStateSnapshot | null | undefined,
|
|
20
|
+
message: string,
|
|
21
|
+
): GameView {
|
|
22
|
+
const view = snapshot?.view;
|
|
23
|
+
if (view === null || typeof view === "undefined") {
|
|
24
|
+
throw new Error(message);
|
|
25
|
+
}
|
|
26
|
+
return view;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Hook to select a derived value from the projected reducer view.
|
|
31
|
+
* Only re-renders when the selected slice changes according to the equalityFn.
|
|
32
|
+
*
|
|
33
|
+
* @param selector - Function to extract data from the projected view
|
|
34
|
+
* @param equalityFn - Optional equality function for selected slice comparison
|
|
35
|
+
* @returns Selected value from the current projected view
|
|
36
|
+
*/
|
|
37
|
+
export function useGameSelector<T>(
|
|
38
|
+
selector: (state: GameView) => T,
|
|
39
|
+
equalityFn: EqualityFn<T> = defaultEquality,
|
|
40
|
+
): T {
|
|
41
|
+
const runtime = useRuntimeContext() as PluginRuntimeAPI;
|
|
42
|
+
const selectorRef = useRef(selector);
|
|
43
|
+
const equalityRef = useRef(equalityFn);
|
|
44
|
+
const cacheRef = useRef<SelectorCache<T> | null>(null);
|
|
45
|
+
|
|
46
|
+
if (selectorRef.current !== selector || equalityRef.current !== equalityFn) {
|
|
47
|
+
selectorRef.current = selector;
|
|
48
|
+
equalityRef.current = equalityFn;
|
|
49
|
+
cacheRef.current = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const message =
|
|
53
|
+
"useGameSelector: Projected view not available. Ensure the reducer-native host payload is initialized.";
|
|
54
|
+
|
|
55
|
+
const getSelectedFromSnapshot = (
|
|
56
|
+
snapshot: PluginStateSnapshot | null | undefined,
|
|
57
|
+
): T => {
|
|
58
|
+
const view = requireProjectedView(snapshot, message);
|
|
59
|
+
const cached = cacheRef.current;
|
|
60
|
+
if (cached && cached.rawView === view) {
|
|
61
|
+
return cached.selected;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const nextSelected = selectorRef.current(view);
|
|
65
|
+
if (cached && equalityRef.current(cached.selected, nextSelected)) {
|
|
66
|
+
cacheRef.current = {
|
|
67
|
+
rawView: view,
|
|
68
|
+
selected: cached.selected,
|
|
69
|
+
};
|
|
70
|
+
return cached.selected;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
cacheRef.current = {
|
|
74
|
+
rawView: view,
|
|
75
|
+
selected: nextSelected,
|
|
76
|
+
};
|
|
77
|
+
return nextSelected;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const subscribe = (onStoreChange: () => void) => {
|
|
81
|
+
if (!runtime.subscribeToState) {
|
|
82
|
+
return () => {};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return runtime.subscribeToState((snapshot) => {
|
|
86
|
+
const previousCache = cacheRef.current;
|
|
87
|
+
const nextSelected = getSelectedFromSnapshot(
|
|
88
|
+
snapshot as PluginStateSnapshot,
|
|
89
|
+
);
|
|
90
|
+
if (
|
|
91
|
+
!previousCache ||
|
|
92
|
+
!equalityRef.current(previousCache.selected, nextSelected)
|
|
93
|
+
) {
|
|
94
|
+
onStoreChange();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const getSnapshot = () =>
|
|
100
|
+
getSelectedFromSnapshot(
|
|
101
|
+
(runtime.getSnapshot?.() ?? null) as PluginStateSnapshot | null,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
105
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { GameView } from "#dreamboard/ui-contract";
|
|
2
|
+
import { useGameSelector } from "./useGameSelector.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook to access the current seat-projected reducer-native view.
|
|
6
|
+
*/
|
|
7
|
+
export function useGameView(): GameView {
|
|
8
|
+
return useGameSelector((view) => view);
|
|
9
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
useArmedInteraction,
|
|
4
|
+
useInteractionDraft,
|
|
5
|
+
useInteractionSubmitting,
|
|
6
|
+
useInteractionUiStore,
|
|
7
|
+
} from "../context/InteractionDraftContext.js";
|
|
8
|
+
import { useClientParamSchema } from "../context/ClientParamSchemaContext.js";
|
|
9
|
+
import { usePluginState } from "../context/PluginStateContext.js";
|
|
10
|
+
import { usePluginSession } from "../context/PluginSessionContext.js";
|
|
11
|
+
import { useRuntimeContext } from "../context/RuntimeContext.js";
|
|
12
|
+
import {
|
|
13
|
+
ValidationError,
|
|
14
|
+
validationErrorFromUnknown,
|
|
15
|
+
} from "../errors/ValidationError.js";
|
|
16
|
+
import type { InteractionDescriptor } from "../types/plugin-state.js";
|
|
17
|
+
import {
|
|
18
|
+
applyInteractionInputDefaults,
|
|
19
|
+
hasInteractionFieldErrors,
|
|
20
|
+
inputByKey,
|
|
21
|
+
isInputValueReady,
|
|
22
|
+
interactionArmScope,
|
|
23
|
+
interactionInputKeys,
|
|
24
|
+
mergeInteractionFieldErrors,
|
|
25
|
+
validateInteractionInputDomains,
|
|
26
|
+
} from "../utils/interaction-inputs.js";
|
|
27
|
+
import type {
|
|
28
|
+
DraftValidation,
|
|
29
|
+
InteractionHandle,
|
|
30
|
+
InteractionHandleStatus,
|
|
31
|
+
InteractionParamsShape,
|
|
32
|
+
} from "./useInteractionHandle.js";
|
|
33
|
+
import {
|
|
34
|
+
interactionUnavailableReason,
|
|
35
|
+
isInteractionAvailable,
|
|
36
|
+
} from "../utils/interaction-status.js";
|
|
37
|
+
import { shouldAutoSubmitInteraction } from "../utils/interaction-router.js";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Look up an interaction descriptor by phase-qualified key on the controlling seat's
|
|
41
|
+
* inbox and return a bound {@link InteractionHandle}. Returns `null`
|
|
42
|
+
* when no matching descriptor is currently projected.
|
|
43
|
+
*
|
|
44
|
+
* Prefer this over manual `inbox.bySurface.panel?.find(...)` + sentinel
|
|
45
|
+
* descriptor patterns — it keeps hook-call order stable and guarantees
|
|
46
|
+
* the handle reflects the freshest descriptor.
|
|
47
|
+
*
|
|
48
|
+
* Types:
|
|
49
|
+
* - `Key` narrows the key literal. When called from the workspace-local
|
|
50
|
+
* `useInteractionByKey` re-export generated in `ui-contract.ts`, `Key` is
|
|
51
|
+
* constrained to the generated `InteractionKey` union so typos become
|
|
52
|
+
* compile errors.
|
|
53
|
+
* - `Params` is the params shape (`InteractionParamsOf<Key>` in the
|
|
54
|
+
* generated re-export). It flows through to `draft`, `submit`,
|
|
55
|
+
* `validate`, and `setInput` for compile-time safety.
|
|
56
|
+
*
|
|
57
|
+
* ```tsx
|
|
58
|
+
* // from the generated workspace re-export
|
|
59
|
+
* const handle = useInteractionByKey("play.placeThingCard");
|
|
60
|
+
* if (!handle) return <Waiting/>;
|
|
61
|
+
* handle.setInput("cardId", card.id); // typed to ThingsDeckCardId
|
|
62
|
+
* await handle.submit();
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function useInteractionByKey<
|
|
66
|
+
Key extends string = string,
|
|
67
|
+
Params extends InteractionParamsShape = InteractionParamsShape,
|
|
68
|
+
DefaultedKeys extends keyof Params & string = never,
|
|
69
|
+
>(
|
|
70
|
+
interactionKey: Key | null | undefined,
|
|
71
|
+
): InteractionHandle<Params, DefaultedKeys> | null {
|
|
72
|
+
const runtime = useRuntimeContext();
|
|
73
|
+
const { controllingPlayerId } = usePluginSession();
|
|
74
|
+
const store = useInteractionUiStore();
|
|
75
|
+
const submittingRef = useRef(false);
|
|
76
|
+
const autoSubmitSignatureRef = useRef<string | null>(null);
|
|
77
|
+
|
|
78
|
+
const descriptors = usePluginState(
|
|
79
|
+
(state) => state.gameplay.availableInteractions ?? [],
|
|
80
|
+
);
|
|
81
|
+
const simultaneousPhase = usePluginState(
|
|
82
|
+
(state) => state.gameplay.simultaneousPhase,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const descriptor = useMemo<InteractionDescriptor | null>(() => {
|
|
86
|
+
if (!interactionKey) return null;
|
|
87
|
+
return descriptors.find((d) => d.interactionKey === interactionKey) ?? null;
|
|
88
|
+
}, [descriptors, interactionKey]);
|
|
89
|
+
|
|
90
|
+
const armScope = descriptor
|
|
91
|
+
? interactionArmScope(descriptor)
|
|
92
|
+
: "interaction:missing";
|
|
93
|
+
const resolvedPhaseName = descriptor?.phaseName;
|
|
94
|
+
const resolvedInteractionId = descriptor?.interactionId ?? "";
|
|
95
|
+
const resolvedInteractionKey = descriptor?.interactionKey ?? "";
|
|
96
|
+
const paramsSchema = useClientParamSchema(
|
|
97
|
+
resolvedPhaseName,
|
|
98
|
+
resolvedInteractionId,
|
|
99
|
+
);
|
|
100
|
+
const draft = useInteractionDraft(resolvedInteractionKey);
|
|
101
|
+
const armedId = useArmedInteraction(armScope);
|
|
102
|
+
const submitting = useInteractionSubmitting(resolvedInteractionKey);
|
|
103
|
+
|
|
104
|
+
const handle = useMemo<InteractionHandle<
|
|
105
|
+
Params,
|
|
106
|
+
DefaultedKeys
|
|
107
|
+
> | null>(() => {
|
|
108
|
+
if (!descriptor) return null;
|
|
109
|
+
const typedDraft = draft as Readonly<Partial<Params>>;
|
|
110
|
+
const typedValues = applyInteractionInputDefaults<Params>(
|
|
111
|
+
descriptor,
|
|
112
|
+
typedDraft,
|
|
113
|
+
) as Readonly<Partial<Params> & Pick<Params, DefaultedKeys>>;
|
|
114
|
+
const inputKeys = interactionInputKeys(descriptor);
|
|
115
|
+
const isReady =
|
|
116
|
+
inputKeys.length === 0
|
|
117
|
+
? true
|
|
118
|
+
: inputKeys.every((key) => {
|
|
119
|
+
const input = inputByKey(descriptor, key);
|
|
120
|
+
const value = (typedValues as Record<string, unknown>)[key];
|
|
121
|
+
return input
|
|
122
|
+
? isInputValueReady(input, value)
|
|
123
|
+
: value !== null && value !== undefined;
|
|
124
|
+
});
|
|
125
|
+
const isArmed = armedId === descriptor.interactionKey;
|
|
126
|
+
const submitted =
|
|
127
|
+
controllingPlayerId !== null &&
|
|
128
|
+
simultaneousPhase?.phaseName === descriptor.phaseName &&
|
|
129
|
+
simultaneousPhase.interactionId === descriptor.interactionId &&
|
|
130
|
+
simultaneousPhase.sealedPlayerIds.includes(controllingPlayerId);
|
|
131
|
+
const status: InteractionHandleStatus = submitted
|
|
132
|
+
? "submitted"
|
|
133
|
+
: submitting
|
|
134
|
+
? "submitting"
|
|
135
|
+
: "open";
|
|
136
|
+
|
|
137
|
+
const requirePlayer = () => {
|
|
138
|
+
if (!controllingPlayerId) {
|
|
139
|
+
throw new Error("useInteractionByKey: no controlling player available");
|
|
140
|
+
}
|
|
141
|
+
return controllingPlayerId;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const submit = async (params?: Params) => {
|
|
145
|
+
if (status !== "open" || submittingRef.current) {
|
|
146
|
+
throw new ValidationError(
|
|
147
|
+
status === "submitted" ? "ALREADY_SUBMITTED" : "SUBMITTING",
|
|
148
|
+
status === "submitted"
|
|
149
|
+
? "Interaction has already been submitted."
|
|
150
|
+
: "Interaction submission is already in progress.",
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
submittingRef.current = true;
|
|
154
|
+
store.setSubmitting(descriptor.interactionKey, true);
|
|
155
|
+
const finalParams = applyInteractionInputDefaults<Params>(
|
|
156
|
+
descriptor,
|
|
157
|
+
params ?? typedValues,
|
|
158
|
+
) as Params;
|
|
159
|
+
try {
|
|
160
|
+
await runtime.submitInteraction(
|
|
161
|
+
requirePlayer(),
|
|
162
|
+
descriptor.interactionId,
|
|
163
|
+
finalParams as Record<string, unknown>,
|
|
164
|
+
);
|
|
165
|
+
store.clearInput(descriptor.interactionKey);
|
|
166
|
+
if (store.getArmed(armScope) === descriptor.interactionKey) {
|
|
167
|
+
store.arm(armScope, null);
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
throw validationErrorFromUnknown(error);
|
|
171
|
+
} finally {
|
|
172
|
+
submittingRef.current = false;
|
|
173
|
+
store.setSubmitting(descriptor.interactionKey, false);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const validate = async (params?: Params) => {
|
|
178
|
+
const finalParams = applyInteractionInputDefaults<Params>(
|
|
179
|
+
descriptor,
|
|
180
|
+
params ?? typedValues,
|
|
181
|
+
) as Params;
|
|
182
|
+
const result = await runtime.validateInteraction(
|
|
183
|
+
requirePlayer(),
|
|
184
|
+
descriptor.interactionId,
|
|
185
|
+
finalParams as Record<string, unknown>,
|
|
186
|
+
);
|
|
187
|
+
if (!result.valid) {
|
|
188
|
+
throw new ValidationError(result.errorCode, result.message);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const validateDraft = (): DraftValidation<Params> => {
|
|
193
|
+
const rawDraft = { ...typedValues } as Record<string, unknown>;
|
|
194
|
+
const missing = inputKeys.filter((key) => {
|
|
195
|
+
const input = inputByKey(descriptor, key);
|
|
196
|
+
const value = rawDraft[key];
|
|
197
|
+
return input
|
|
198
|
+
? !isInputValueReady(input, value)
|
|
199
|
+
: value === null || value === undefined;
|
|
200
|
+
}) as Array<keyof Params & string>;
|
|
201
|
+
const domainFieldErrors = validateInteractionInputDomains(
|
|
202
|
+
descriptor,
|
|
203
|
+
rawDraft,
|
|
204
|
+
) as Partial<Record<keyof Params & string, readonly string[]>>;
|
|
205
|
+
|
|
206
|
+
if (!paramsSchema) {
|
|
207
|
+
if (
|
|
208
|
+
missing.length > 0 ||
|
|
209
|
+
hasInteractionFieldErrors(domainFieldErrors)
|
|
210
|
+
) {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
fieldErrors: domainFieldErrors,
|
|
214
|
+
formErrors: [],
|
|
215
|
+
missing,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
ok: true,
|
|
220
|
+
params: rawDraft as Params,
|
|
221
|
+
fieldErrors: {},
|
|
222
|
+
formErrors: [],
|
|
223
|
+
missing: [],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const result = paramsSchema.safeParse(rawDraft);
|
|
228
|
+
if (result.success) {
|
|
229
|
+
if (hasInteractionFieldErrors(domainFieldErrors)) {
|
|
230
|
+
return {
|
|
231
|
+
ok: false,
|
|
232
|
+
fieldErrors: domainFieldErrors,
|
|
233
|
+
formErrors: [],
|
|
234
|
+
missing,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
ok: true,
|
|
239
|
+
params: result.data as Params,
|
|
240
|
+
fieldErrors: {},
|
|
241
|
+
formErrors: [],
|
|
242
|
+
missing: [],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const fieldErrors: Partial<
|
|
247
|
+
Record<keyof Params & string, readonly string[]>
|
|
248
|
+
> = {};
|
|
249
|
+
const formErrors: string[] = [];
|
|
250
|
+
for (const issue of result.error.issues) {
|
|
251
|
+
const [first] = issue.path;
|
|
252
|
+
if (typeof first === "string" && inputKeys.includes(first)) {
|
|
253
|
+
const key = first as keyof Params & string;
|
|
254
|
+
fieldErrors[key] = [...(fieldErrors[key] ?? []), issue.message];
|
|
255
|
+
} else {
|
|
256
|
+
formErrors.push(issue.message);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
ok: false,
|
|
261
|
+
fieldErrors: mergeInteractionFieldErrors(
|
|
262
|
+
fieldErrors,
|
|
263
|
+
domainFieldErrors,
|
|
264
|
+
) as Partial<Record<keyof Params & string, readonly string[]>>,
|
|
265
|
+
formErrors,
|
|
266
|
+
missing,
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const validateDraftServer = async () => {
|
|
271
|
+
await validate({ ...typedValues } as Params);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const submitDraft = async () => {
|
|
275
|
+
const validation = validateDraft();
|
|
276
|
+
if (!validation.ok) {
|
|
277
|
+
const message =
|
|
278
|
+
validation.formErrors[0] ??
|
|
279
|
+
Object.values(validation.fieldErrors).flat()[0] ??
|
|
280
|
+
(validation.missing.length > 0
|
|
281
|
+
? "Required inputs are missing"
|
|
282
|
+
: "Draft validation failed");
|
|
283
|
+
throw new ValidationError("INVALID_DRAFT", message);
|
|
284
|
+
}
|
|
285
|
+
await submit(validation.params);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
descriptor,
|
|
290
|
+
commit: descriptor.commit,
|
|
291
|
+
available: isInteractionAvailable(descriptor),
|
|
292
|
+
unavailableReason: interactionUnavailableReason(descriptor),
|
|
293
|
+
status,
|
|
294
|
+
submit,
|
|
295
|
+
validate,
|
|
296
|
+
validateDraft,
|
|
297
|
+
validateDraftServer,
|
|
298
|
+
submitDraft,
|
|
299
|
+
draft: typedDraft,
|
|
300
|
+
values: typedValues,
|
|
301
|
+
setInput: <K extends keyof Params & string>(key: K, value: Params[K]) =>
|
|
302
|
+
store.setInput(descriptor.interactionKey, key, value as unknown),
|
|
303
|
+
clearInput: (key?: keyof Params & string) =>
|
|
304
|
+
store.clearInput(descriptor.interactionKey, key),
|
|
305
|
+
isReady,
|
|
306
|
+
isArmed,
|
|
307
|
+
arm: () => store.arm(armScope, descriptor.interactionKey),
|
|
308
|
+
disarm: () => {
|
|
309
|
+
if (store.getArmed(armScope) === descriptor.interactionKey) {
|
|
310
|
+
store.arm(armScope, null);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}, [
|
|
315
|
+
descriptor,
|
|
316
|
+
draft,
|
|
317
|
+
armedId,
|
|
318
|
+
armScope,
|
|
319
|
+
runtime,
|
|
320
|
+
store,
|
|
321
|
+
controllingPlayerId,
|
|
322
|
+
paramsSchema,
|
|
323
|
+
simultaneousPhase,
|
|
324
|
+
submitting,
|
|
325
|
+
]);
|
|
326
|
+
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
if (!handle) {
|
|
329
|
+
autoSubmitSignatureRef.current = null;
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (!shouldAutoSubmitInteraction(handle.descriptor)) {
|
|
333
|
+
autoSubmitSignatureRef.current = null;
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (!handle.available || handle.status !== "open" || !handle.isReady) {
|
|
337
|
+
if (!handle.isReady) autoSubmitSignatureRef.current = null;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const validation = handle.validateDraft();
|
|
341
|
+
if (!validation.ok) return;
|
|
342
|
+
const signature = `${handle.descriptor.interactionKey}:${JSON.stringify(
|
|
343
|
+
validation.params,
|
|
344
|
+
)}`;
|
|
345
|
+
if (autoSubmitSignatureRef.current === signature) return;
|
|
346
|
+
autoSubmitSignatureRef.current = signature;
|
|
347
|
+
void handle.submit(validation.params).catch(() => {
|
|
348
|
+
// Runtime error channels surface the failure. Keep the draft intact and
|
|
349
|
+
// suppress repeated attempts until the player changes the draft.
|
|
350
|
+
});
|
|
351
|
+
}, [handle]);
|
|
352
|
+
|
|
353
|
+
return handle;
|
|
354
|
+
}
|