improve web client
This commit is contained in:
parent
3574542a94
commit
dc00a78b57
|
@ -1,8 +1,7 @@
|
||||||
/* eslint-disable no-restricted-syntax */
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef, MutableRefObject, Fragment } from 'react';
|
||||||
import useWebSocketModule, { ReadyState } from 'react-use-websocket';
|
import useWebSocketModule, { ReadyState } from 'react-use-websocket';
|
||||||
import { Maybe, Optional, doesExist } from '@apextoaster/js-utils';
|
import { Maybe, doesExist } from '@apextoaster/js-utils';
|
||||||
import List from '@mui/material/List';
|
import List from '@mui/material/List';
|
||||||
import ListItem from '@mui/material/ListItem';
|
import ListItem from '@mui/material/ListItem';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
|
@ -16,7 +15,7 @@ import Alert from '@mui/material/Alert';
|
||||||
import Switch from '@mui/material/Switch';
|
import Switch from '@mui/material/Switch';
|
||||||
import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
|
import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
|
||||||
import { TreeItem } from '@mui/x-tree-view/TreeItem';
|
import { TreeItem } from '@mui/x-tree-view/TreeItem';
|
||||||
import { CssBaseline, PaletteMode, ThemeProvider, createTheme } from '@mui/material';
|
import { Button, CssBaseline, Dialog, DialogContent, DialogTitle, PaletteMode, ThemeProvider, createTheme } from '@mui/material';
|
||||||
|
|
||||||
import { formatters } from './format.js';
|
import { formatters } from './format.js';
|
||||||
|
|
||||||
|
@ -74,7 +73,7 @@ export function WorldItem(props: EventItemProps) {
|
||||||
|
|
||||||
return <ListItem alignItems="flex-start">
|
return <ListItem alignItems="flex-start">
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar alt={step} src="/static/images/avatar/1.jpg" />
|
<Avatar alt={step.toString()} src="/static/images/avatar/1.jpg" />
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={theme}
|
primary={theme}
|
||||||
|
@ -165,32 +164,94 @@ export interface World {
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WorldPanel(props: { world: Maybe<World> }) {
|
export type SetDetails = (entity: Maybe<Item | Actor | Room>) => void;
|
||||||
const { world } = props;
|
|
||||||
|
|
||||||
return <Stack direction="column">
|
export function ItemItem(props: { item: Item; setDetails: SetDetails }) {
|
||||||
|
const { item, setDetails } = props;
|
||||||
|
|
||||||
|
return <TreeItem itemId={item.name} label={item.name}>
|
||||||
|
<TreeItem itemId={`${item.name}-details`} label="Details" onClick={() => setDetails(item)} />
|
||||||
|
</TreeItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ActorItem(props: { actor: Actor; setDetails: SetDetails }) {
|
||||||
|
const { actor, setDetails } = props;
|
||||||
|
|
||||||
|
return <TreeItem itemId={actor.name} label={actor.name}>
|
||||||
|
<TreeItem itemId={`${actor.name}-details`} label="Details" onClick={() => setDetails(actor)} />
|
||||||
|
<TreeItem itemId={`${actor.name}-items`} label="Items">
|
||||||
|
{actor.items.map((item) => <ItemItem item={item} setDetails={setDetails} />)}
|
||||||
|
</TreeItem>
|
||||||
|
</TreeItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RoomItem(props: { room: Room; setDetails: SetDetails }) {
|
||||||
|
const { room, setDetails } = props;
|
||||||
|
|
||||||
|
return <TreeItem itemId={room.name} label={room.name}>
|
||||||
|
<TreeItem itemId={`${room.name}-details`} label="Details" onClick={() => setDetails(room)} />
|
||||||
|
<TreeItem itemId={`${room.name}-actors`} label="Actors">
|
||||||
|
{room.actors.map((actor) => <ActorItem actor={actor} setDetails={setDetails} />)}
|
||||||
|
</TreeItem>
|
||||||
|
<TreeItem itemId={`${room.name}-items`} label="Items">
|
||||||
|
{room.items.map((item) => <ItemItem item={item} setDetails={setDetails} />)}
|
||||||
|
</TreeItem>
|
||||||
|
</TreeItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WorldPanel(props: { world: Maybe<World>; setDetails: SetDetails }) {
|
||||||
|
const { world, setDetails } = props;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
if (!doesExist(world)) {
|
||||||
|
return <Typography variant="h4">
|
||||||
|
No world data available
|
||||||
|
</Typography>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Stack direction="column" sx={{ minWidth: 600 }}>
|
||||||
<Typography variant="h4">
|
<Typography variant="h4">
|
||||||
World: {world?.name}
|
World: {world.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
Theme: {world?.theme}
|
Theme: {world.theme}
|
||||||
</Typography>
|
</Typography>
|
||||||
<SimpleTreeView>
|
<SimpleTreeView>
|
||||||
{world?.rooms.map((room) => <TreeItem itemId={room.name} label={room.name}>
|
{world.rooms.map((room) => <RoomItem room={room} setDetails={setDetails} />)}
|
||||||
{room.actors.map((actor) => <TreeItem itemId={actor.name} label={actor.name}>
|
|
||||||
{actor.items.map((item) => <TreeItem itemId={item.name} label={item.name} />
|
|
||||||
)}
|
|
||||||
</TreeItem>
|
|
||||||
)}
|
|
||||||
{room.items.map((item) => <TreeItem itemId={item.name} label={item.name} />
|
|
||||||
)}
|
|
||||||
</TreeItem>
|
|
||||||
)}
|
|
||||||
</SimpleTreeView>
|
</SimpleTreeView>
|
||||||
</Stack>;
|
</Stack>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function EntityDetails(props: { entity: Maybe<Item | Actor | Room>; close: () => void }) {
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
if (!doesExist(props.entity)) {
|
||||||
|
return <Fragment />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Fragment>
|
||||||
|
<DialogTitle>{props.entity.name}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>
|
||||||
|
{props.entity.description}
|
||||||
|
</Typography>
|
||||||
|
<Button onClick={() => props.close()}>Close</Button>
|
||||||
|
</DialogContent>
|
||||||
|
</Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DetailDialog(props: { setDetails: SetDetails; details: Maybe<Item | Actor | Room> }) {
|
||||||
|
const { details, setDetails } = props;
|
||||||
|
|
||||||
|
return <Dialog
|
||||||
|
open={doesExist(details)}
|
||||||
|
onClose={() => setDetails(undefined)}
|
||||||
|
>
|
||||||
|
<EntityDetails entity={details} close={() => setDetails(undefined)} />
|
||||||
|
</Dialog>;
|
||||||
|
}
|
||||||
|
|
||||||
export function App(props: AppProps) {
|
export function App(props: AppProps) {
|
||||||
|
const [ detailEntity, setDetailEntity ] = useState<Maybe<Item | Actor | Room>>(undefined);
|
||||||
const [ world, setWorld ] = useState<Maybe<World>>(undefined);
|
const [ world, setWorld ] = useState<Maybe<World>>(undefined);
|
||||||
const [ themeMode, setThemeMode ] = useState('light');
|
const [ themeMode, setThemeMode ] = useState('light');
|
||||||
const [ history, setHistory ] = useState<Array<string>>([]);
|
const [ history, setHistory ] = useState<Array<string>>([]);
|
||||||
|
@ -220,9 +281,8 @@ export function App(props: AppProps) {
|
||||||
|
|
||||||
return <ThemeProvider theme={theme}>
|
return <ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Container maxWidth='lg'>
|
<DetailDialog details={detailEntity} setDetails={setDetailEntity} />
|
||||||
<Stack direction="row">
|
<Container maxWidth='xl'>
|
||||||
<WorldPanel world={world} />
|
|
||||||
<Stack direction="column">
|
<Stack direction="column">
|
||||||
<Alert icon={false} severity="success">
|
<Alert icon={false} severity="success">
|
||||||
<Stack direction="row" alignItems="center" gap={4}>
|
<Stack direction="row" alignItems="center" gap={4}>
|
||||||
|
@ -237,11 +297,15 @@ export function App(props: AppProps) {
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
<Stack direction="row">
|
||||||
|
<WorldPanel world={world} setDetails={setDetailEntity} />
|
||||||
|
<Stack direction="column" sx={{ minWidth: 800 }}>
|
||||||
<List sx={{ width: '100%', bgcolor: 'background.paper' }}>
|
<List sx={{ width: '100%', bgcolor: 'background.paper' }}>
|
||||||
{interleave(items)}
|
{interleave(items)}
|
||||||
</List>
|
</List>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
</ThemeProvider>;
|
</ThemeProvider>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ export function formatWorld(data: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatters: Record<string, any> = {
|
export const formatters: Record<string, any> = {
|
||||||
input: formatInput,
|
action: formatInput,
|
||||||
result: formatResult,
|
result: formatResult,
|
||||||
world: formatWorld,
|
world: formatWorld,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
<title>Text World</title>
|
<title>Text World</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="history">
|
<div id="history">
|
||||||
<ol id="history"></ol>
|
|
||||||
</div>
|
</div>
|
||||||
<script src="./bundle/main.js" type="module"></script>
|
<script src="./bundle/main.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in New Issue