diff --git a/src/App.tsx b/src/App.tsx index 82cf6ee..467b632 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import React from "react"; import { StartScreen } from "./components/StartScreen.js"; -import { GameScreen } from "./components/GameScreen.js"; +import { GameScreen } from "./components/GameScreen/index.js"; import { GameContext } from "./components/GameContext.js"; import { ScoreScreen } from "./components/ScoreScreen.js"; import { HelpScreen } from "./components/HelpScreen.js"; diff --git a/src/components/GameScreen/Divider.tsx b/src/components/GameScreen/Divider.tsx new file mode 100644 index 0000000..346df61 --- /dev/null +++ b/src/components/GameScreen/Divider.tsx @@ -0,0 +1,21 @@ +import React, { useState, useEffect } from "react"; +import { Text, Box, DOMElement, measureElement } from "ink"; + +const dividerChar = "─"; + +export function Divider({ containerRef }: { containerRef: DOMElement | null }) { + const [width, setWidth] = useState(0); + + useEffect(() => { + if (containerRef) { + const output = measureElement(containerRef); + setWidth(output.width); + } + }, [containerRef]); + + return ( + + {dividerChar.repeat(width)} + + ); +} diff --git a/src/components/TextInput/index.tsx b/src/components/GameScreen/TextInput/index.tsx similarity index 100% rename from src/components/TextInput/index.tsx rename to src/components/GameScreen/TextInput/index.tsx diff --git a/src/components/TextInput/use-text-input-state.ts b/src/components/GameScreen/TextInput/use-text-input-state.ts similarity index 100% rename from src/components/TextInput/use-text-input-state.ts rename to src/components/GameScreen/TextInput/use-text-input-state.ts diff --git a/src/components/TextInput/use-text-input.ts b/src/components/GameScreen/TextInput/use-text-input.ts similarity index 100% rename from src/components/TextInput/use-text-input.ts rename to src/components/GameScreen/TextInput/use-text-input.ts diff --git a/src/components/GameScreen/Typed.tsx b/src/components/GameScreen/Typed.tsx new file mode 100644 index 0000000..116c984 --- /dev/null +++ b/src/components/GameScreen/Typed.tsx @@ -0,0 +1,46 @@ +import React, { useState, useEffect } from "react"; +import { Text, type TextProps, useInput } from "ink"; +import { GameContext } from "../GameContext.js"; + +export function Typed({ + text, + enabled = true, + children, + ...props +}: React.PropsWithChildren<{ text: string; enabled?: boolean } & TextProps>) { + const settings = GameContext.useSelector((snapshot) => snapshot.context.settings); + const [current, setCurrent] = useState(0); + const [finished, setFinished] = useState(false); + const shouldAnimate = !settings.disableAnimations && enabled; + + useEffect(() => { + const id = setTimeout(() => { + if (shouldAnimate && current < text.length) { + setCurrent(current + 1); + } else { + clearTimeout(id); + setFinished(true); + } + }, 50); + + return () => clearTimeout(id); + }, [text, shouldAnimate, current]); + + useInput( + (input) => { + if (input === " ") { + setCurrent(text.length); + } + }, + { + isActive: shouldAnimate && current < text.length, + }, + ); + + return ( + <> + {shouldAnimate ? text.slice(0, current) : text} + {shouldAnimate ? (finished ? children : null) : children} + + ); +} diff --git a/src/components/GameScreen.tsx b/src/components/GameScreen/index.tsx similarity index 89% rename from src/components/GameScreen.tsx rename to src/components/GameScreen/index.tsx index 3b31f86..f1525c6 100644 --- a/src/components/GameScreen.tsx +++ b/src/components/GameScreen/index.tsx @@ -1,13 +1,13 @@ -import React, { useEffect, useState } from "react"; -import { Box, DOMElement, measureElement, Text, TextProps, useInput } from "ink"; +import React, { useState, useDeferredValue } from "react"; import chalk from "chalk"; -import { GameContext } from "./GameContext.js"; +import { Box, DOMElement, Text, useInput } from "ink"; import { Alert, Badge, ConfirmInput } from "@inkjs/ui"; +import { GameContext } from "../GameContext.js"; import { TextInput } from "./TextInput/index.js"; -import { calculateCostForRepair, getAvailableStorage, getShipStatus } from "../store/utils.js"; -import { goods, ports } from "../store/constants.js"; - -const dividerChar = "─"; +import { Divider } from "./Divider.js"; +import { Typed } from "./Typed.js"; +import { calculateCostForRepair, getAvailableStorage, getShipStatus } from "../../store/utils.js"; +import { goods, ports } from "../../store/constants.js"; export function GameScreen() { const [ref, setRef] = useState(null); @@ -41,23 +41,6 @@ export function GameScreen() { ); } -function Divider({ containerRef }: { containerRef: DOMElement | null }) { - const [width, setWidth] = useState(0); - - useEffect(() => { - if (containerRef) { - const output = measureElement(containerRef); - setWidth(output.width); - } - }, [containerRef]); - - return ( - - {dividerChar.repeat(width)} - - ); -} - function StatusBar() { const context = GameContext.useSelector((snapshot) => snapshot.context); const shipHealth = getShipStatus(context.ship.health); @@ -470,46 +453,3 @@ function RetireAction() { ); } - -function Typed({ - text, - enabled = true, - children, - ...props -}: React.PropsWithChildren<{ text: string; enabled?: boolean } & TextProps>) { - const settings = GameContext.useSelector((snapshot) => snapshot.context.settings); - const [current, setCurrent] = useState(0); - const [finished, setFinished] = useState(false); - const shouldAnimate = !settings.disableAnimations && enabled; - - useEffect(() => { - const id = setTimeout(() => { - if (shouldAnimate && current < text.length) { - setCurrent(current + 1); - } else { - clearTimeout(id); - setFinished(true); - } - }, 50); - - return () => clearTimeout(id); - }, [text, shouldAnimate, current]); - - useInput( - (input) => { - if (input === " ") { - setCurrent(text.length); - } - }, - { - isActive: shouldAnimate && current < text.length, - }, - ); - - return ( - <> - {shouldAnimate ? text.slice(0, current) : text} - {shouldAnimate ? (finished ? children : null) : children} - - ); -}