feat: add shared runtime input device layer
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -23,6 +23,15 @@ import type {
|
||||
SwapPuzzlePiecesRequest,
|
||||
} from '../../../packages/shared/src/contracts/puzzleRuntimeSession';
|
||||
import { useResolvedAssetReadUrl } from '../../hooks/useResolvedAssetReadUrl';
|
||||
import {
|
||||
createRuntimeDragInputController,
|
||||
createRuntimeInputPointFromClient,
|
||||
createRuntimeInputPointFromNormalized,
|
||||
readRuntimeInputElementBounds,
|
||||
resolveRuntimeInputGridCell,
|
||||
type RuntimeDragInputSession,
|
||||
type RuntimeInputPoint,
|
||||
} from '../../services/input-devices';
|
||||
import { useMocapInput } from '../../services/useMocapInput';
|
||||
import { CHROME_ICONS, getNineSliceStyle, UI_CHROME } from '../../uiAssets';
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
@@ -211,6 +220,7 @@ const PUZZLE_HINT_DEMO_DURATION_MS = 1_250;
|
||||
const PUZZLE_PIECE_PRESS_HAPTIC_PATTERN_MS = 12;
|
||||
const PUZZLE_EXIT_REMODEL_PROMPT_STORAGE_PREFIX =
|
||||
'genarrative.puzzle-runtime.exit-remodel-prompt.v1';
|
||||
const PUZZLE_MOCAP_DRAG_INPUT_ID = 'mocap:primary-hand';
|
||||
|
||||
const shownExitRemodelPromptProfileIds = new Set<string>();
|
||||
|
||||
@@ -290,6 +300,11 @@ type PuzzleMocapCursorState = {
|
||||
state: string;
|
||||
};
|
||||
|
||||
type PuzzleRuntimeDragTargetState = {
|
||||
pieceId: string;
|
||||
groupId: string | null;
|
||||
};
|
||||
|
||||
function triggerPuzzlePiecePressHapticFeedback() {
|
||||
if (typeof navigator === 'undefined') {
|
||||
return;
|
||||
@@ -328,6 +343,8 @@ export function PuzzleRuntimeShell({
|
||||
const mergedGroupSvgIdPrefix = sanitizeSvgId(useId());
|
||||
const authUi = useAuthUi();
|
||||
const [selectedPieceId, setSelectedPieceId] = useState<string | null>(null);
|
||||
const selectedPieceIdRef = useRef<string | null>(null);
|
||||
const selectedPieceBeforeInputRef = useRef<string | null>(null);
|
||||
const [isSettingsPanelOpen, setIsSettingsPanelOpen] = useState(false);
|
||||
const [isExitRemodelPromptOpen, setIsExitRemodelPromptOpen] =
|
||||
useState(false);
|
||||
@@ -354,7 +371,7 @@ export function PuzzleRuntimeShell({
|
||||
const timeExpiredSyncKeyRef = useRef<string | null>(null);
|
||||
const dragSessionRef = useRef<{
|
||||
pieceId: string;
|
||||
pointerId: number;
|
||||
inputId: string;
|
||||
dragging: boolean;
|
||||
startX: number;
|
||||
startY: number;
|
||||
@@ -377,7 +394,10 @@ export function PuzzleRuntimeShell({
|
||||
const [mocapCursor, setMocapCursor] = useState<PuzzleMocapCursorState | null>(
|
||||
null,
|
||||
);
|
||||
const mocapDragRef = useRef<{pieceId: string} | null>(null);
|
||||
const runtimeDragInputControllerRef = useRef(
|
||||
createRuntimeDragInputController<string>(),
|
||||
);
|
||||
const draggingTargetRef = useRef<PuzzleRuntimeDragTargetState | null>(null);
|
||||
const [dismissedClearKey, setDismissedClearKey] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
@@ -400,6 +420,8 @@ export function PuzzleRuntimeShell({
|
||||
? 'failed'
|
||||
: currentLevel.status
|
||||
: 'playing';
|
||||
const isInteractionLocked =
|
||||
isBusy || runtimeStatus !== 'playing' || Boolean(propDialog);
|
||||
const clearResultKey = currentLevel
|
||||
? `${run?.runId ?? 'run'}:${currentLevel.profileId}:${currentLevel.levelIndex}`
|
||||
: null;
|
||||
@@ -409,12 +431,19 @@ export function PuzzleRuntimeShell({
|
||||
currentLevel?.coverImageSrc ?? null,
|
||||
);
|
||||
const mocapInput = useMocapInput({enabled: runtimeStatus === 'playing'});
|
||||
const primaryMocapHand = mocapInput.latestCommand?.primaryHand;
|
||||
const primaryMocapHandState = primaryMocapHand?.state;
|
||||
const primaryMocapHandX = primaryMocapHand?.x;
|
||||
const primaryMocapHandY = primaryMocapHand?.y;
|
||||
const mocapActionsLabel =
|
||||
mocapInput.latestCommand?.actions.length
|
||||
? mocapInput.latestCommand.actions.join(', ')
|
||||
: '无';
|
||||
const mocapHandLabel = mocapInput.latestCommand?.primaryHand
|
||||
? `${mocapInput.latestCommand.primaryHand.state} @ ${mocapInput.latestCommand.primaryHand.x.toFixed(2)}, ${mocapInput.latestCommand.primaryHand.y.toFixed(2)}`
|
||||
const mocapHandLabel =
|
||||
primaryMocapHandState &&
|
||||
typeof primaryMocapHandX === 'number' &&
|
||||
typeof primaryMocapHandY === 'number'
|
||||
? `${primaryMocapHandState} @ ${primaryMocapHandX.toFixed(2)}, ${primaryMocapHandY.toFixed(2)}`
|
||||
: '无';
|
||||
const mocapParseWarningLabel = mocapInput.latestCommand?.parseWarnings?.length
|
||||
? mocapInput.latestCommand.parseWarnings.join(';')
|
||||
@@ -425,6 +454,11 @@ export function PuzzleRuntimeShell({
|
||||
currentLevelRef.current = currentLevel;
|
||||
}, [currentLevel]);
|
||||
|
||||
const commitSelectedPieceId = (pieceId: string | null) => {
|
||||
selectedPieceIdRef.current = pieceId;
|
||||
setSelectedPieceId(pieceId);
|
||||
};
|
||||
|
||||
const pieces = useMemo<PuzzleBoardPieceViewModel[]>(() => {
|
||||
if (!board) {
|
||||
return [];
|
||||
@@ -586,13 +620,18 @@ export function PuzzleRuntimeShell({
|
||||
dragVisualFrameRef.current = null;
|
||||
};
|
||||
|
||||
const resetDragInteraction = () => {
|
||||
const resetDragInteractionState = () => {
|
||||
cancelDragVisualFrame();
|
||||
dragOffsetRef.current = null;
|
||||
dragSessionRef.current = null;
|
||||
draggingTargetRef.current = null;
|
||||
resetDragVisualTarget();
|
||||
};
|
||||
|
||||
const resetDragInteraction = () => {
|
||||
runtimeDragInputControllerRef.current.cancel();
|
||||
};
|
||||
|
||||
const flushDragVisual = () => {
|
||||
dragVisualFrameRef.current = null;
|
||||
const dragSession = dragSessionRef.current;
|
||||
@@ -602,7 +641,8 @@ export function PuzzleRuntimeShell({
|
||||
}
|
||||
|
||||
const piece = pieceById.get(dragSession.pieceId) ?? null;
|
||||
const groupId = piece?.mergedGroupId ?? null;
|
||||
const groupId =
|
||||
draggingTargetRef.current?.groupId ?? piece?.mergedGroupId ?? null;
|
||||
const nextTarget = {
|
||||
pieceId: dragSession.pieceId,
|
||||
groupId,
|
||||
@@ -808,6 +848,221 @@ export function PuzzleRuntimeShell({
|
||||
];
|
||||
}, [clearResultKey, currentLevel, dismissedClearKey]);
|
||||
|
||||
const handlePieceTap = (
|
||||
pieceId: string,
|
||||
selectedPieceIdBeforeInput: string | null,
|
||||
) => {
|
||||
if (isInteractionLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedPieceIdBeforeInput) {
|
||||
commitSelectedPieceId(pieceId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedPieceIdBeforeInput === pieceId) {
|
||||
commitSelectedPieceId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
onSwapPieces({
|
||||
firstPieceId: selectedPieceIdBeforeInput,
|
||||
secondPieceId: pieceId,
|
||||
});
|
||||
commitSelectedPieceId(null);
|
||||
};
|
||||
|
||||
const resolvePuzzleRuntimeDragTarget = (
|
||||
pieceId: string,
|
||||
): PuzzleRuntimeDragTargetState | null => {
|
||||
const sourcePiece = pieceById.get(pieceId) ?? null;
|
||||
if (!sourcePiece) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
pieceId: sourcePiece.pieceId,
|
||||
groupId: sourcePiece.mergedGroupId ?? null,
|
||||
};
|
||||
};
|
||||
|
||||
const commitPuzzleRuntimeDrag = (
|
||||
target: PuzzleRuntimeDragTargetState | null,
|
||||
point: RuntimeInputPoint,
|
||||
) => {
|
||||
const dragSession = dragSessionRef.current;
|
||||
if (!target || !dragSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetCell = board
|
||||
? resolveRuntimeInputGridCell(point, board)
|
||||
: null;
|
||||
if (!targetCell) {
|
||||
return;
|
||||
}
|
||||
|
||||
onDragPiece({
|
||||
pieceId: target.pieceId,
|
||||
targetRow: targetCell.row,
|
||||
targetCol: targetCell.col,
|
||||
});
|
||||
};
|
||||
|
||||
const resolveBoardInputPointFromClient = (
|
||||
clientX: number,
|
||||
clientY: number,
|
||||
) =>
|
||||
createRuntimeInputPointFromClient(
|
||||
clientX,
|
||||
clientY,
|
||||
readRuntimeInputElementBounds(boardRef.current),
|
||||
);
|
||||
|
||||
const resolveBoardInputPointFromNormalized = (
|
||||
normalizedX: number,
|
||||
normalizedY: number,
|
||||
) =>
|
||||
createRuntimeInputPointFromNormalized(
|
||||
normalizedX,
|
||||
normalizedY,
|
||||
readRuntimeInputElementBounds(boardRef.current),
|
||||
);
|
||||
|
||||
const syncRuntimeDragFromController = (
|
||||
session: RuntimeDragInputSession<string> | null,
|
||||
) => {
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
draggingTargetRef.current = resolvePuzzleRuntimeDragTarget(session.targetId);
|
||||
dragSessionRef.current = {
|
||||
pieceId: session.targetId,
|
||||
inputId: session.inputId,
|
||||
dragging: session.dragging,
|
||||
startX: session.startPoint.clientX,
|
||||
startY: session.startPoint.clientY,
|
||||
currentX: session.currentPoint.clientX,
|
||||
currentY: session.currentPoint.clientY,
|
||||
};
|
||||
|
||||
if (session.dragging) {
|
||||
flushDragVisual();
|
||||
scheduleDragVisual();
|
||||
}
|
||||
};
|
||||
|
||||
runtimeDragInputControllerRef.current.setOptions({
|
||||
dragThresholdPx: 8,
|
||||
onPress: (session) => {
|
||||
draggingTargetRef.current = resolvePuzzleRuntimeDragTarget(session.targetId);
|
||||
syncRuntimeDragFromController(session);
|
||||
selectedPieceBeforeInputRef.current = selectedPieceIdRef.current;
|
||||
commitSelectedPieceId(session.targetId);
|
||||
triggerPuzzlePiecePressHapticFeedback();
|
||||
},
|
||||
onDragStart: (session) => {
|
||||
draggingTargetRef.current = resolvePuzzleRuntimeDragTarget(session.targetId);
|
||||
syncRuntimeDragFromController(session);
|
||||
},
|
||||
onDragMove: (session) => {
|
||||
syncRuntimeDragFromController(session);
|
||||
},
|
||||
onDrop: (session) => {
|
||||
draggingTargetRef.current = resolvePuzzleRuntimeDragTarget(session.targetId);
|
||||
syncRuntimeDragFromController(session);
|
||||
commitPuzzleRuntimeDrag(draggingTargetRef.current, session.currentPoint);
|
||||
commitSelectedPieceId(null);
|
||||
selectedPieceBeforeInputRef.current = null;
|
||||
resetDragInteractionState();
|
||||
},
|
||||
onTap: (session) => {
|
||||
handlePieceTap(session.targetId, selectedPieceBeforeInputRef.current);
|
||||
selectedPieceBeforeInputRef.current = null;
|
||||
resetDragInteractionState();
|
||||
},
|
||||
onCancel: () => {
|
||||
commitSelectedPieceId(selectedPieceBeforeInputRef.current);
|
||||
selectedPieceBeforeInputRef.current = null;
|
||||
resetDragInteractionState();
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const activeSession = runtimeDragInputControllerRef.current.getSession();
|
||||
if (!board || runtimeStatus !== 'playing' || isInteractionLocked) {
|
||||
runtimeDragInputControllerRef.current.cancel();
|
||||
setMocapCursor(null);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!primaryMocapHandState ||
|
||||
typeof primaryMocapHandX !== 'number' ||
|
||||
typeof primaryMocapHandY !== 'number'
|
||||
) {
|
||||
runtimeDragInputControllerRef.current.cancel(PUZZLE_MOCAP_DRAG_INPUT_ID);
|
||||
setMocapCursor(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setMocapCursor({
|
||||
x: primaryMocapHandX,
|
||||
y: primaryMocapHandY,
|
||||
state: primaryMocapHandState,
|
||||
});
|
||||
const handPoint = resolveBoardInputPointFromNormalized(
|
||||
primaryMocapHandX,
|
||||
primaryMocapHandY,
|
||||
);
|
||||
if (primaryMocapHandState === 'grab') {
|
||||
if (activeSession?.inputId !== PUZZLE_MOCAP_DRAG_INPUT_ID) {
|
||||
const sourceCell = resolveRuntimeInputGridCell(handPoint, board);
|
||||
const sourcePiece = sourceCell
|
||||
? pieceByCell.get(`${sourceCell.row}:${sourceCell.col}`) ?? null
|
||||
: null;
|
||||
if (!sourcePiece) {
|
||||
runtimeDragInputControllerRef.current.cancel(
|
||||
PUZZLE_MOCAP_DRAG_INPUT_ID,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runtimeDragInputControllerRef.current.press({
|
||||
targetId: sourcePiece.pieceId,
|
||||
inputId: PUZZLE_MOCAP_DRAG_INPUT_ID,
|
||||
deviceKind: 'mocap',
|
||||
point: handPoint,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
runtimeDragInputControllerRef.current.move({
|
||||
inputId: PUZZLE_MOCAP_DRAG_INPUT_ID,
|
||||
point: handPoint,
|
||||
forceDragging: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeSession?.inputId === PUZZLE_MOCAP_DRAG_INPUT_ID) {
|
||||
runtimeDragInputControllerRef.current.release({
|
||||
inputId: PUZZLE_MOCAP_DRAG_INPUT_ID,
|
||||
point: handPoint,
|
||||
forceDrop: activeSession.deviceKind === 'mocap',
|
||||
});
|
||||
}
|
||||
}, [
|
||||
board,
|
||||
isInteractionLocked,
|
||||
pieceByCell,
|
||||
primaryMocapHandState,
|
||||
primaryMocapHandX,
|
||||
primaryMocapHandY,
|
||||
runtimeStatus,
|
||||
]);
|
||||
|
||||
if (!run || !currentLevel || !board) {
|
||||
return (
|
||||
<div
|
||||
@@ -821,131 +1076,12 @@ export function PuzzleRuntimeShell({
|
||||
);
|
||||
}
|
||||
|
||||
const handlePieceClick = (pieceId: string) => {
|
||||
if (isInteractionLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedPieceId) {
|
||||
setSelectedPieceId(pieceId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedPieceId === pieceId) {
|
||||
setSelectedPieceId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
onSwapPieces({
|
||||
firstPieceId: selectedPieceId,
|
||||
secondPieceId: pieceId,
|
||||
});
|
||||
setSelectedPieceId(null);
|
||||
};
|
||||
|
||||
const resolveBoardCellFromPointer = (clientX: number, clientY: number) => {
|
||||
const boardElement = boardRef.current;
|
||||
if (!boardElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rect = boardElement.getBoundingClientRect();
|
||||
if (
|
||||
clientX < rect.left ||
|
||||
clientX > rect.right ||
|
||||
clientY < rect.top ||
|
||||
clientY > rect.bottom
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const relativeX = clientX - rect.left;
|
||||
const relativeY = clientY - rect.top;
|
||||
const col = Math.min(
|
||||
board.cols - 1,
|
||||
Math.max(0, Math.floor((relativeX / rect.width) * board.cols)),
|
||||
);
|
||||
const row = Math.min(
|
||||
board.rows - 1,
|
||||
Math.max(0, Math.floor((relativeY / rect.height) * board.rows)),
|
||||
);
|
||||
|
||||
return { row, col };
|
||||
};
|
||||
|
||||
const resolveMocapTargetCell = (x: number, y: number) => ({
|
||||
row: Math.min(board.rows - 1, Math.max(0, Math.floor(y * board.rows))),
|
||||
col: Math.min(board.cols - 1, Math.max(0, Math.floor(x * board.cols))),
|
||||
});
|
||||
|
||||
const handleMocapInputCommand = () => {
|
||||
const hand = mocapInput.latestCommand?.primaryHand;
|
||||
if (runtimeStatus !== 'playing' || isInteractionLocked || !hand) {
|
||||
mocapDragRef.current = null;
|
||||
setMocapCursor(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setMocapCursor({x: hand.x, y: hand.y, state: hand.state});
|
||||
if (hand.state === 'grab') {
|
||||
if (mocapDragRef.current) {
|
||||
return;
|
||||
}
|
||||
const sourceCell = resolveMocapTargetCell(hand.x, hand.y);
|
||||
const sourcePiece = pieceByCell.get(`${sourceCell.row}:${sourceCell.col}`) ?? null;
|
||||
if (!sourcePiece || sourcePiece.mergedGroupId) {
|
||||
return;
|
||||
}
|
||||
mocapDragRef.current = {pieceId: sourcePiece.pieceId};
|
||||
setSelectedPieceId(sourcePiece.pieceId);
|
||||
triggerPuzzlePiecePressHapticFeedback();
|
||||
return;
|
||||
}
|
||||
|
||||
const draggingPiece = mocapDragRef.current;
|
||||
if (!draggingPiece) {
|
||||
return;
|
||||
}
|
||||
const targetCell = resolveMocapTargetCell(hand.x, hand.y);
|
||||
mocapDragRef.current = null;
|
||||
setSelectedPieceId(null);
|
||||
onDragPiece({
|
||||
pieceId: draggingPiece.pieceId,
|
||||
targetRow: targetCell.row,
|
||||
targetCol: targetCell.col,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePiecePointerUp = (
|
||||
pieceId: string,
|
||||
event: React.PointerEvent<HTMLDivElement>,
|
||||
) => {
|
||||
const currentDragSession = dragSessionRef.current;
|
||||
if (!currentDragSession || currentDragSession.pieceId !== pieceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handlePiecePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
||||
|
||||
if (currentDragSession.dragging) {
|
||||
const targetCell = resolveBoardCellFromPointer(
|
||||
event.clientX,
|
||||
event.clientY,
|
||||
);
|
||||
resetDragInteraction();
|
||||
if (targetCell) {
|
||||
onDragPiece({
|
||||
pieceId,
|
||||
targetRow: targetCell.row,
|
||||
targetCol: targetCell.col,
|
||||
});
|
||||
}
|
||||
setSelectedPieceId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
resetDragInteraction();
|
||||
handlePieceClick(pieceId);
|
||||
runtimeDragInputControllerRef.current.release({
|
||||
inputId: `pointer:${event.pointerId}`,
|
||||
point: resolveBoardInputPointFromClient(event.clientX, event.clientY),
|
||||
});
|
||||
};
|
||||
|
||||
const handlePiecePointerDown = (
|
||||
@@ -958,46 +1094,20 @@ export function PuzzleRuntimeShell({
|
||||
event.preventDefault();
|
||||
resetDragInteraction();
|
||||
event.currentTarget.setPointerCapture?.(event.pointerId);
|
||||
// 按下可交互拼图片时立即给移动端短震反馈,点击选择与拖起都会有同一套手感。
|
||||
triggerPuzzlePiecePressHapticFeedback();
|
||||
dragSessionRef.current = {
|
||||
pieceId,
|
||||
pointerId: event.pointerId,
|
||||
dragging: false,
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
currentX: event.clientX,
|
||||
currentY: event.clientY,
|
||||
};
|
||||
runtimeDragInputControllerRef.current.press({
|
||||
targetId: pieceId,
|
||||
inputId: `pointer:${event.pointerId}`,
|
||||
deviceKind: 'pointer',
|
||||
point: resolveBoardInputPointFromClient(event.clientX, event.clientY),
|
||||
});
|
||||
};
|
||||
|
||||
const handlePiecePointerMove = (
|
||||
pieceId: string,
|
||||
event: React.PointerEvent<HTMLDivElement>,
|
||||
) => {
|
||||
const dragSession = dragSessionRef.current;
|
||||
if (
|
||||
!dragSession ||
|
||||
dragSession.pieceId !== pieceId ||
|
||||
dragSession.pointerId !== event.pointerId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handlePiecePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
const deltaX = event.clientX - dragSession.startX;
|
||||
const deltaY = event.clientY - dragSession.startY;
|
||||
const dragging = dragSession.dragging || Math.hypot(deltaX, deltaY) >= 8;
|
||||
dragSession.dragging = dragging;
|
||||
dragSession.currentX = event.clientX;
|
||||
dragSession.currentY = event.clientY;
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 首帧拖拽反馈立即落到 DOM,确保层级提升不会滞后一帧;后续仍保留 raf 兜底连续刷新。
|
||||
flushDragVisual();
|
||||
scheduleDragVisual();
|
||||
runtimeDragInputControllerRef.current.move({
|
||||
inputId: `pointer:${event.pointerId}`,
|
||||
point: resolveBoardInputPointFromClient(event.clientX, event.clientY),
|
||||
});
|
||||
};
|
||||
|
||||
const draggingPieceId = dragRenderTarget?.pieceId ?? null;
|
||||
@@ -1037,8 +1147,6 @@ export function PuzzleRuntimeShell({
|
||||
currentLevel.status === 'cleared' &&
|
||||
dismissedClearKey !== clearResultKey &&
|
||||
isClearResultReady;
|
||||
const isInteractionLocked =
|
||||
isBusy || runtimeStatus !== 'playing' || Boolean(propDialog);
|
||||
const handleBackRequest = () => {
|
||||
if (hideExitControls) {
|
||||
return;
|
||||
@@ -1150,10 +1258,6 @@ export function PuzzleRuntimeShell({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleMocapInputCommand();
|
||||
}, [mocapInput.latestCommand?.primaryHand]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`puzzle-runtime-shell ${embedded ? 'relative h-full min-h-0 w-full' : 'fixed inset-0 z-[100]'} flex justify-center`}
|
||||
@@ -1311,11 +1415,11 @@ export function PuzzleRuntimeShell({
|
||||
if (!piece || isMerged) {
|
||||
return;
|
||||
}
|
||||
handlePiecePointerMove(piece.pieceId, event);
|
||||
handlePiecePointerMove(event);
|
||||
}}
|
||||
onPointerUp={(event) => {
|
||||
if (piece && !isMerged) {
|
||||
handlePiecePointerUp(piece.pieceId, event);
|
||||
handlePiecePointerUp(event);
|
||||
}
|
||||
}}
|
||||
onPointerCancel={() => {
|
||||
@@ -1461,10 +1565,10 @@ export function PuzzleRuntimeShell({
|
||||
handlePiecePointerDown(piece.pieceId, event);
|
||||
}}
|
||||
onPointerMove={(event) => {
|
||||
handlePiecePointerMove(piece.pieceId, event);
|
||||
handlePiecePointerMove(event);
|
||||
}}
|
||||
onPointerUp={(event) => {
|
||||
handlePiecePointerUp(piece.pieceId, event);
|
||||
handlePiecePointerUp(event);
|
||||
}}
|
||||
onPointerCancel={() => {
|
||||
resetDragInteraction();
|
||||
|
||||
Reference in New Issue
Block a user