import { Maybe, doesExist, mustDefault, mustExist } from '@apextoaster/js-utils'; import { Alert, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, FormControlLabel, MenuItem, Stack, Switch, TextField } from '@mui/material'; import React from 'react'; import { useStore } from 'zustand'; import { World, BooleanParameter, NumberParameter, StringParameter, Parameter, Action } from './models'; import { StoreState, store } from './store'; // region parameter components export interface BooleanParameterProps { name: string; parameter: BooleanParameter; setParameter: (value: boolean) => void; } export function BooleanParameterItem(props: BooleanParameterProps) { const { name, setParameter } = props; return setParameter(event.target.checked)} />} label={name} />; } export function EnumParameterItem(props: NumberParameterProps | StringParameterProps) { const { name, parameter, setParameter } = props; const enumValues = mustExist(parameter.enum); const defaultValue = mustDefault(parameter.default, enumValues[0]); return setParameter(event.target.value)} > {enumValues.map((value) => {value})} ; } export interface NumberParameterProps { name: string; parameter: NumberParameter; setParameter: (value: number) => void; } export function NumberParameterItem(props: NumberParameterProps) { const { name, parameter, setParameter } = props; if (doesExist(parameter.enum)) { return ; } return setParameter(parseFloat(event.target.value))} />; } export interface StringParameterProps { name: string; parameter: StringParameter; setParameter: (value: string) => void; } export function StringParameterItem(props: StringParameterProps) { const { name, parameter, setParameter } = props; if (doesExist(parameter.enum)) { return ; } return setParameter(event.target.value)} />; } export interface UnknownParameterProps { name: string; } export function UnknownParameter(props: UnknownParameterProps) { const { name } = props; return Unknown parameter type: {name}; } // endregion export const SIGNIFICANT_PARAMETERS = ['character', 'direction', 'item', 'room', 'target']; export function listCharacters(world: World) { return world.rooms.flatMap((room) => room.characters); } export function listItems(world: World) { return world.rooms.flatMap((room) => room.items); } export function listPortals(world: World) { return world.rooms.flatMap((room) => room.portals); } export function enumerateSignificantParameterValues(name: string, world: World) { switch (name) { case 'character': return listCharacters(world).map((character) => character.name); case 'direction': return listPortals(world).map((portal) => portal.name); case 'item': return listItems(world).map((item) => item.name); case 'room': return world.rooms.map((room) => room.name); case 'target': { const characters = listCharacters(world); const items = listItems(world); return [ ...characters.map((character) => character.name), ...items.map((item) => item.name), ]; } default: return []; } } export function convertSignificantParameter(name: string, parameter: Parameter, world: Maybe): Parameter { if (parameter.type === 'boolean') { return parameter; } if (doesExist(world) && SIGNIFICANT_PARAMETERS.includes(name)) { return { ...parameter, enum: enumerateSignificantParameterValues(name, world), }; } return parameter; } export function selectWorld(state: StoreState) { return { world: state.world, }; } export function formatAction(action: string, parameters: Record) { return `~${action}:${Object.entries(parameters).map(([name, value]) => `${name}=${value}`).join(',')}`; } export function makeDefaultParameterValues(parameters: Record) { return Object.entries(parameters).reduce((acc, [name, parameter]) => { switch (parameter.type) { case 'boolean': return { ...acc, [name]: mustDefault(parameter.default, false) }; case 'number': return { ...acc, [name]: mustDefault(parameter.default, 0) }; case 'string': return { ...acc, [name]: mustDefault(parameter.default, '') }; default: return acc; } }, {} as Record); } export interface PromptActionProps { action: Action; setAction: (action: string) => void; } export function PromptAction(props: PromptActionProps) { const { action, setAction } = props; const { world } = useStore(store, selectWorld); // initialize with default values const [parameterValues, setParameterValues] = React.useState(makeDefaultParameterValues(action.function.parameters.properties)); // create an input for each parameter const inputs = Object.entries(action.function.parameters.properties).filter(([name, _parameter]) => name !== 'unused').map(([name, parameter]) => { const convertedParameter = convertSignificantParameter(name, parameter, world); switch (convertedParameter.type) { case 'string': return { setParameterValues((old) => ({ ...old, [name]: value })); }} />; case 'number': return { setParameterValues((old) => ({ ...old, [name]: value })); }} />; case 'boolean': return { setParameterValues((old) => ({ ...old, [name]: value })); }} />; default: return ; } }); return {inputs} ; } export function selectPromptEvent(state: StoreState) { return { promptEvent: state.promptEvent, }; } export interface PromptDialogProps { sendInput: (input: string) => void; skipPrompt: () => void; } export function PromptDialog(props: PromptDialogProps) { const { sendInput, skipPrompt } = props; const { promptEvent } = useStore(store, selectPromptEvent); const [input, setInput] = React.useState(''); // eslint-disable-next-line no-restricted-syntax if (!doesExist(promptEvent)) { return ; } return It's your turn, {promptEvent.character.name}! {promptEvent.prompt} Select parameters first, then click the label to act. {promptEvent.actions.map((action: Action) => )} setInput(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') { sendInput(input); setInput(''); } }} /> ; }