CLAUDE: Add Undo Last Play feature for game rollback
- Added rollback_play WebSocket handler (handlers.py:1632) - Accepts game_id and num_plays (default: 1) - Validates game state and play count - Broadcasts play_rolled_back and game_state_update events - Full error handling with rate limiting - Added undoLastPlay action to useGameActions composable - Emits rollback_play event to backend - Added Undo button to game page ([id].vue) - Amber floating action button with undo arrow icon - Positioned above substitutions button - Only visible when game is active and has plays 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c27a652e54
commit
920d1c599c
File diff suppressed because it is too large
Load Diff
@ -246,6 +246,27 @@ export function useGameActions(gameId?: string) {
|
||||
uiStore.showInfo('Requesting pitching change...', 3000)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Undo/Rollback Actions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Undo the last N plays
|
||||
* Rolls back plays from the database and reconstructs game state
|
||||
*/
|
||||
function undoLastPlay(numPlays: number = 1) {
|
||||
if (!validateConnection()) return
|
||||
|
||||
console.log('[GameActions] Undoing last', numPlays, 'play(s)')
|
||||
|
||||
socket.value!.emit('rollback_play', {
|
||||
game_id: currentGameId.value!,
|
||||
num_plays: numPlays,
|
||||
})
|
||||
|
||||
uiStore.showInfo(`Undoing ${numPlays} play(s)...`, 3000)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Data Request Actions
|
||||
// ============================================================================
|
||||
@ -314,6 +335,9 @@ export function useGameActions(gameId?: string) {
|
||||
requestDefensiveReplacement,
|
||||
requestPitchingChange,
|
||||
|
||||
// Undo/Rollback
|
||||
undoLastPlay,
|
||||
|
||||
// Data requests
|
||||
getLineup,
|
||||
getBoxScore,
|
||||
|
||||
@ -238,17 +238,33 @@
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<!-- Floating Action Button for Substitutions -->
|
||||
<button
|
||||
v-if="canMakeSubstitutions"
|
||||
class="fixed bottom-6 right-6 w-16 h-16 bg-primary hover:bg-blue-700 text-white rounded-full shadow-lg flex items-center justify-center z-40 transition-all hover:scale-110"
|
||||
aria-label="Open Substitutions"
|
||||
@click="showSubstitutions = true"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Floating Action Buttons -->
|
||||
<div class="fixed bottom-6 right-6 flex flex-col gap-3 z-40">
|
||||
<!-- Undo Last Play Button -->
|
||||
<button
|
||||
v-if="canUndo"
|
||||
class="w-14 h-14 bg-amber-500 hover:bg-amber-600 text-white rounded-full shadow-lg flex items-center justify-center transition-all hover:scale-110"
|
||||
aria-label="Undo Last Play"
|
||||
title="Undo Last Play"
|
||||
@click="handleUndoLastPlay"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Substitutions Button -->
|
||||
<button
|
||||
v-if="canMakeSubstitutions"
|
||||
class="w-16 h-16 bg-primary hover:bg-blue-700 text-white rounded-full shadow-lg flex items-center justify-center transition-all hover:scale-110"
|
||||
aria-label="Open Substitutions"
|
||||
@click="showSubstitutions = true"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -290,6 +306,9 @@ const { socket, isConnected, connectionError, connect } = useWebSocket()
|
||||
// useGameActions will create its own computed internally if needed
|
||||
const actions = useGameActions(route.params.id as string)
|
||||
|
||||
// Destructure undoLastPlay for the undo button
|
||||
const { undoLastPlay } = actions
|
||||
|
||||
// Game state from store
|
||||
const gameState = computed(() => gameStore.gameState)
|
||||
const playHistory = computed(() => gameStore.playHistory)
|
||||
@ -395,6 +414,11 @@ const canMakeSubstitutions = computed(() => {
|
||||
return gameState.value?.status === 'active' && isMyTurn.value
|
||||
})
|
||||
|
||||
const canUndo = computed(() => {
|
||||
// Can only undo if game is active and there are plays to undo
|
||||
return gameState.value?.status === 'active' && (gameState.value?.play_count ?? 0) > 0
|
||||
})
|
||||
|
||||
// Lineup helpers for substitutions
|
||||
const currentLineup = computed(() => {
|
||||
if (!myTeamId.value) return []
|
||||
@ -543,6 +567,12 @@ const handleSubstitutionCancel = () => {
|
||||
showSubstitutions.value = false
|
||||
}
|
||||
|
||||
// Undo handler
|
||||
const handleUndoLastPlay = () => {
|
||||
console.log('[Game Page] Undoing last play')
|
||||
undoLastPlay(1)
|
||||
}
|
||||
|
||||
// Measure ScoreBoard height dynamically
|
||||
const updateScoreBoardHeight = () => {
|
||||
if (scoreBoardRef.value) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user