mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 16:25:51 +00:00
feat: 修复小细节
This commit is contained in:
@@ -4,20 +4,14 @@ import type { SpeciesId, SpriteCache } from '../types'
|
|||||||
import { getSpeciesData } from '../dex/species'
|
import { getSpeciesData } from '../dex/species'
|
||||||
import { getSpritesDir } from './storage'
|
import { getSpritesDir } from './storage'
|
||||||
|
|
||||||
const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/HRKings/pokemonsay-newgenerations/master/pokemons'
|
const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/claude-code-best/pokemonsay-newgenerations/master/pokemons'
|
||||||
|
|
||||||
/** Mapping of speciesId to cow file prefix */
|
/**
|
||||||
const COW_FILE_MAP: Record<SpeciesId, string> = {
|
* Build cow file name from dex number: NNN.cow
|
||||||
bulbasaur: '001_bulbasaur',
|
*/
|
||||||
ivysaur: '002_ivysaur',
|
function cowFileName(speciesId: SpeciesId): string {
|
||||||
venusaur: '003_venusaur',
|
const { dexNumber } = getSpeciesData(speciesId)
|
||||||
charmander: '004_charmander',
|
return `${String(dexNumber).padStart(3, '0')}.cow`
|
||||||
charmeleon: '005_charmeleon',
|
|
||||||
charizard: '006_charizard',
|
|
||||||
squirtle: '007_squirtle',
|
|
||||||
wartortle: '008_wartortle',
|
|
||||||
blastoise: '009_blastoise',
|
|
||||||
pikachu: '025_pikachu',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,8 +26,7 @@ export function loadSprite(speciesId: SpeciesId): SpriteCache | null {
|
|||||||
try {
|
try {
|
||||||
const raw = readFileSync(filePath, 'utf-8')
|
const raw = readFileSync(filePath, 'utf-8')
|
||||||
return JSON.parse(raw) as SpriteCache
|
return JSON.parse(raw) as SpriteCache
|
||||||
} catch (e) {
|
} catch {
|
||||||
console.error(`[buddy] Failed to load sprite cache for ${speciesId}:`, e)
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,10 +40,8 @@ export async function fetchAndCacheSprite(speciesId: SpeciesId): Promise<SpriteC
|
|||||||
const cached = loadSprite(speciesId)
|
const cached = loadSprite(speciesId)
|
||||||
if (cached) return cached
|
if (cached) return cached
|
||||||
|
|
||||||
const cowFileName = COW_FILE_MAP[speciesId]
|
const fileName = cowFileName(speciesId)
|
||||||
if (!cowFileName) return null
|
const url = `${GITHUB_RAW_BASE}/${fileName}`
|
||||||
|
|
||||||
const url = `${GITHUB_RAW_BASE}/${cowFileName}.cow`
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
@@ -63,7 +54,7 @@ export async function fetchAndCacheSprite(speciesId: SpeciesId): Promise<SpriteC
|
|||||||
const sprite: SpriteCache = {
|
const sprite: SpriteCache = {
|
||||||
speciesId,
|
speciesId,
|
||||||
lines,
|
lines,
|
||||||
width: Math.max(...lines.map((l) => stripAnsi(l).length)),
|
width: Math.max(...lines.map(l => stripAnsi(l).length)),
|
||||||
height: lines.length,
|
height: lines.length,
|
||||||
fetchedAt: Date.now(),
|
fetchedAt: Date.now(),
|
||||||
}
|
}
|
||||||
@@ -74,8 +65,7 @@ export async function fetchAndCacheSprite(speciesId: SpeciesId): Promise<SpriteC
|
|||||||
writeFileSync(filePath, JSON.stringify(sprite, null, 2))
|
writeFileSync(filePath, JSON.stringify(sprite, null, 2))
|
||||||
|
|
||||||
return sprite
|
return sprite
|
||||||
} catch (e) {
|
} catch {
|
||||||
console.error(`[buddy] Failed to fetch sprite for ${speciesId}:`, e)
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +109,7 @@ function convertCowToLines(cowContent: string): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trim trailing whitespace on each line (preserve leading for alignment)
|
// Trim trailing whitespace on each line (preserve leading for alignment)
|
||||||
lines = lines.map((line) => line.trimEnd())
|
lines = lines.map(line => line.trimEnd())
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Box, Text, Pane, Tab, Tabs, useInput, useTabHeaderFocus, type Color } from '@anthropic/ink';
|
import { Box, Text, Pane, Tab, Tabs, useInput, type Color } from '@anthropic/ink';
|
||||||
import { useSetAppState } from '../../state/AppState.js';
|
import { useSetAppState } from '../../state/AppState.js';
|
||||||
import { useKeybinding } from '../../keybindings/useKeybinding.js';
|
import { useKeybinding } from '../../keybindings/useKeybinding.js';
|
||||||
import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js';
|
import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js';
|
||||||
@@ -72,6 +72,20 @@ export function BuddyPanel({ buddyData, spriteLines, onClose }: BuddyPanelProps)
|
|||||||
isActive: true,
|
isActive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Tab / Shift+Tab to switch between tabs
|
||||||
|
const TAB_ORDER = ['Buddy', 'PC Box', 'Pokédex', 'Egg']
|
||||||
|
useInput((_input, key) => {
|
||||||
|
if (key.tab) {
|
||||||
|
setSelectedTab(prev => {
|
||||||
|
const idx = TAB_ORDER.indexOf(prev)
|
||||||
|
if (key.shift) {
|
||||||
|
return TAB_ORDER[(idx - 1 + TAB_ORDER.length) % TAB_ORDER.length]!
|
||||||
|
}
|
||||||
|
return TAB_ORDER[(idx + 1) % TAB_ORDER.length]!
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const updateData = (updated: BuddyData) => {
|
const updateData = (updated: BuddyData) => {
|
||||||
setData(updated);
|
setData(updated);
|
||||||
saveBuddyData(updated);
|
saveBuddyData(updated);
|
||||||
@@ -100,7 +114,7 @@ export function BuddyPanel({ buddyData, spriteLines, onClose }: BuddyPanelProps)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Pane color="permission">
|
<Pane color="permission">
|
||||||
<Tabs color="permission" selectedTab={selectedTab} onTabChange={setSelectedTab} initialHeaderFocused={true}>
|
<Tabs color="permission" selectedTab={selectedTab} onTabChange={setSelectedTab} disableNavigation={true}>
|
||||||
{tabs}
|
{tabs}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Pane>
|
</Pane>
|
||||||
@@ -125,11 +139,11 @@ function PartyView({
|
|||||||
|
|
||||||
useInput((_input, key) => {
|
useInput((_input, key) => {
|
||||||
if (!isActive) return;
|
if (!isActive) return;
|
||||||
if (_input === 'a' || _input === 'A') {
|
if (key.leftArrow) {
|
||||||
setFocusedSlot(prev => (prev > 0 ? prev - 1 : 5));
|
setFocusedSlot(prev => (prev > 0 ? prev - 1 : 5));
|
||||||
setTick(t => t + 1);
|
setTick(t => t + 1);
|
||||||
setStatusMsg(null);
|
setStatusMsg(null);
|
||||||
} else if (_input === 'd' || _input === 'D') {
|
} else if (key.rightArrow) {
|
||||||
setFocusedSlot(prev => (prev < 5 ? prev + 1 : 0));
|
setFocusedSlot(prev => (prev < 5 ? prev + 1 : 0));
|
||||||
setTick(t => t + 1);
|
setTick(t => t + 1);
|
||||||
setStatusMsg(null);
|
setStatusMsg(null);
|
||||||
@@ -205,7 +219,7 @@ function PartyView({
|
|||||||
|
|
||||||
{/* Hint */}
|
{/* Hint */}
|
||||||
<Box justifyContent="center">
|
<Box justifyContent="center">
|
||||||
<Text color={GRAY} dimColor>a/d navigate · Enter swap · X remove</Text>
|
<Text color={GRAY} dimColor>←/→ navigate · Enter swap · X remove</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Selected creature detail — key forces remount on slot change */}
|
{/* Selected creature detail — key forces remount on slot change */}
|
||||||
@@ -817,7 +831,6 @@ const PARTY_SLOTS = 6
|
|||||||
type Panel = 'party' | 'box'
|
type Panel = 'party' | 'box'
|
||||||
|
|
||||||
function PcBoxTab({ data, onUpdate, isActive }: { data: BuddyData; onUpdate: (d: BuddyData) => void; isActive: boolean }) {
|
function PcBoxTab({ data, onUpdate, isActive }: { data: BuddyData; onUpdate: (d: BuddyData) => void; isActive: boolean }) {
|
||||||
const { headerFocused, blurHeader, focusHeader } = useTabHeaderFocus()
|
|
||||||
const [boxIdx, setBoxIdx] = useState(0)
|
const [boxIdx, setBoxIdx] = useState(0)
|
||||||
const [panel, setPanel] = useState<Panel>('box')
|
const [panel, setPanel] = useState<Panel>('box')
|
||||||
const [partyCursor, setPartyCursor] = useState(0) // 0-5
|
const [partyCursor, setPartyCursor] = useState(0) // 0-5
|
||||||
@@ -825,9 +838,6 @@ function PcBoxTab({ data, onUpdate, isActive }: { data: BuddyData; onUpdate: (d:
|
|||||||
const [held, setHeld] = useState<{ id: string; from: Panel; partySlot?: number; boxSlot?: number } | null>(null)
|
const [held, setHeld] = useState<{ id: string; from: Panel; partySlot?: number; boxSlot?: number } | null>(null)
|
||||||
const [statusMsg, setStatusMsg] = useState<string | null>(null)
|
const [statusMsg, setStatusMsg] = useState<string | null>(null)
|
||||||
|
|
||||||
// Blur header on mount so left/right goes to box grid, not tab switching
|
|
||||||
React.useEffect(() => { blurHeader() }, [blurHeader])
|
|
||||||
|
|
||||||
const partySet = new Set(data.party.filter((id): id is string => id !== null))
|
const partySet = new Set(data.party.filter((id): id is string => id !== null))
|
||||||
const box = data.boxes[boxIdx]!
|
const box = data.boxes[boxIdx]!
|
||||||
const boxRows = Math.ceil(BOX_SIZE / BOX_COLS)
|
const boxRows = Math.ceil(BOX_SIZE / BOX_COLS)
|
||||||
@@ -839,11 +849,6 @@ function PcBoxTab({ data, onUpdate, isActive }: { data: BuddyData; onUpdate: (d:
|
|||||||
|
|
||||||
useInput((_input, key) => {
|
useInput((_input, key) => {
|
||||||
if (!isActive) return
|
if (!isActive) return
|
||||||
// When header is focused, only handle Tab to give focus back to content
|
|
||||||
if (headerFocused) {
|
|
||||||
if (key.tab) { blurHeader() }
|
|
||||||
return // let Tabs handle left/right
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch box with ,/. regardless of panel
|
// Switch box with ,/. regardless of panel
|
||||||
if (_input === ',' || _input === '<') {
|
if (_input === ',' || _input === '<') {
|
||||||
|
|||||||
Reference in New Issue
Block a user