Skip to content

Commit

Permalink
Queue start signal being sent.
Browse files Browse the repository at this point in the history
  • Loading branch information
leighmacdonald committed Feb 2, 2025
1 parent 6df7248 commit e490200
Show file tree
Hide file tree
Showing 20 changed files with 1,164 additions and 651 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"steamid": "^2.1.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0",
"uuidv7": "^1.0.2",
"video-react": "^0.16.0",
"vite": "^5.4.11",
"vite-plugin-html": "^3.2.2",
Expand Down
9 changes: 9 additions & 0 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 34 additions & 8 deletions frontend/src/api/queue.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
import { readAccessToken } from '../util/auth/readAccessToken.ts';
import { PermissionLevel } from './common.ts';

export enum Operation {
Ping,
Pong,
Join,
Leave,
JoinQueue,
LeaveQueue,
MessageSend,
MessageRecv
MessageRecv,
StateUpdate,
StartGame
}

export type QueueMember = {
name: string;
steam_id: string;
hash: string;
};

export type queuePayload<T> = {
op: Operation;
payload: T;
};

export type pingPayload = queuePayload<{ created_on: Date }>;

export type ServerQueueState = {
server_id: number;
members: string[];
};

export type ServerQueueMessage = {
steam_id: string;
created_on: Date;
Expand All @@ -27,15 +39,29 @@ export type ServerQueueMessage = {
id: string;
};

export type JoinQueuePayload = {
servers: number[];
};

export type LeaveQueuePayload = JoinQueuePayload;

export const websocketURL = () => {
let protocol = 'ws';
if (location.protocol === 'https:') {
protocol = 'wss:';
}
const token = readAccessToken();
return `${protocol}://${location.host}/ws?token=${token}`;
return `${protocol}://${location.host}/ws`;
};

export type Member = {
name: string;
steam_id: string;
hash: string;
};

export const isOperationType = (message: WebSocketEventMap['message'], opType: Operation) => {
return (JSON.parse(message.data) as queuePayload<never>).op === opType;
export type ClientStatePayload = {
update_users: boolean;
update_servers: boolean;
servers: ServerQueueState[];
users: Member[];
};
207 changes: 97 additions & 110 deletions frontend/src/component/QueueChat.tsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,46 @@
import { FormEvent, useCallback, useEffect, useRef, useState } from 'react';
import useWebSocket, { ReadyState } from 'react-use-websocket';
import HourglassBottomIcon from '@mui/icons-material/HourglassBottom';
import SendIcon from '@mui/icons-material/Send';
import Avatar from '@mui/material/Avatar';
import Collapse from '@mui/material/Collapse';
import Paper from '@mui/material/Paper';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { defaultAvatarHash, Operation, PermissionLevel, queuePayload, ServerQueueMessage, websocketURL } from '../api';
import { useTheme } from '@mui/material/styles';
import { PermissionLevel, ServerQueueMessage } from '../api';
import { useAuth } from '../hooks/useAuth.ts';
import { transformCreatedOnDate } from '../util/time.ts';
import { ContainerWithHeader } from './ContainerWithHeader.tsx';
import { useQueueCtx } from '../hooks/useQueueCtx.ts';
import { avatarHashToURL } from '../util/text.tsx';
import { ButtonLink } from './ButtonLink.tsx';
import { LoadingPlaceholder } from './LoadingPlaceholder.tsx';
import { SubmitButton } from './modal/Buttons.tsx';

export const QueueChat = () => {
const [isReady, setIsReady] = useState(false);
const [messages, setMessages] = useState<ServerQueueMessage[]>([]);
const { messages, isReady, sendMessage, showChat } = useQueueCtx();
const { hasPermission } = useAuth();
const [msg, setMsg] = useState<string>('');
const [sending, setSending] = useState(false);
const messagesEndRef = useRef<null | HTMLDivElement>(null);
const { hasPermission } = useAuth();

const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
};

useEffect(() => {
scrollToBottom();
}, [messages]);

const { readyState, sendJsonMessage } = useWebSocket(websocketURL(), {
filter: (message) => {
const req = JSON.parse(message.data) as queuePayload<ServerQueueMessage>;
if (req.op == Operation.MessageRecv) {
setMessages((prev) => [...prev, transformCreatedOnDate(req.payload)]);

return true;
}
return false;
},
share: true
});

useEffect(() => {
switch (readyState) {
case ReadyState.OPEN:
setIsReady(true);
setMessages((prevState) => [
...prevState,
{
created_on: new Date(),
body_md: 'Connected to queue',
avatarhash: '',
permission_level: PermissionLevel.Reserved,
personaname: 'SYSTEM',
steam_id: 'SYSTEM'
} as ServerQueueMessage
]);
break;
case ReadyState.CLOSED:
setMessages((prevState) => [
...prevState,
{
created_on: new Date(),
body_md: 'Disconnected from queue',
avatarhash: '',
permission_level: PermissionLevel.Reserved,
personaname: 'SYSTEM',
steam_id: 'SYSTEM'
} as ServerQueueMessage
]);
break;
default:
setIsReady(false);
}
}, [readyState]);

const onSubmit = useCallback(
async (event: FormEvent<HTMLFormElement | HTMLDivElement>) => {
event.preventDefault();

if (msg.length > 0) {
setSending(true);
sendJsonMessage<queuePayload<ServerQueueMessage>>({
op: Operation.MessageSend,
payload: {
body_md: msg,
personaname: 'queue',
permission_level: PermissionLevel.Reserved,
avatarhash: defaultAvatarHash,
steam_id: 'queue',
created_on: new Date(),
id: '0194ba54-f9cc-7d10-bbe6-1296e6aa939a'
}
});
sendMessage(msg);
setMsg('');
setSending(false);
}
Expand All @@ -106,51 +53,32 @@ export const QueueChat = () => {
}

return (
<ContainerWithHeader title={'Queue Chat'}>
<Grid
container
maxHeight={200}
minHeight={200}
overflow={'hidden'}
<Collapse in={showChat}>
<Paper>
<Stack
maxHeight={200}
minHeight={200}
overflow={'auto'}
sx={{ overflowX: 'hidden' }}
direction={'column'}
padding={1}
>
{!isReady ? (
<LoadingPlaceholder></LoadingPlaceholder>
) : (
messages.map((message) => {
return <ChatRow message={message} key={message.id} />;
})
)}
<span ref={messagesEndRef} key={'hi'} />
</Stack>

//direction="column"
// sx={{
// justifyContent: 'flex-start',
// alignItems: 'flex-start'
// }}
>
{!isReady && <LoadingPlaceholder></LoadingPlaceholder>}
{isReady &&
messages.map((message) => {
return (
<Grid
xs={12}
key={`msg-id-${message.avatarhash}-${message.created_on}-${message.body_md}`}
overflow={'scroll'}
>
<Grid container>
<Grid xs={2}>
<Typography variant="body2" color="textSecondary">
{message.steam_id}:
</Typography>
</Grid>
<Grid xs={10}>
<Typography variant="body1" color="text">
{message.id} || {message.body_md}
</Typography>
</Grid>
</Grid>
</Grid>
);
})}
</Grid>
<Grid container>
{isReady && (
<Grid container padding={1}>
<Grid xs={12}>
<form onSubmit={onSubmit}>
<Stack direction={'row'}>
<Stack direction={'row'} spacing={1}>
<TextField
disabled={sending}
disabled={sending || !isReady}
onSubmit={onSubmit}
fullWidth={true}
size={'small'}
Expand All @@ -168,8 +96,67 @@ export const QueueChat = () => {
</Stack>
</form>
</Grid>
)}
</Grid>
</Paper>
</Collapse>
);
};

const ChatName = ({
steam_id,
personaname,
avatarhash
}: {
steam_id: string;
personaname: string;
avatarhash: string;
}) => {
const theme = useTheme();
return (
<ButtonLink
fullWidth={true}
size={'small'}
to={'/profile/$steamId'}
params={{ steamId: steam_id }}
sx={{
justifyContent: 'flex-start',
padding: 0,
margin: 0,
'&:hover': {
cursor: 'pointer',
backgroundColor: theme.palette.background.default
}
}}
startIcon={
<Avatar
alt={personaname}
src={avatarHashToURL(avatarhash, 'small')}
variant={'square'}
sx={{ height: '16px', width: '16px' }}
/>
}
>
<Typography fontWeight={'bold'} color={theme.palette.text.primary} variant={'body1'}>
{personaname != '' ? personaname : steam_id}
</Typography>
</ButtonLink>
);
};
const ChatRow = ({ message }: { message: ServerQueueMessage }) => {
return (
<Grid container key={`${message.id}-id`} spacing={1}>
<Grid xs={2} alignItems="flex-start" justifyContent="flex-start">
<ChatName
personaname={message.personaname}
steam_id={message.steam_id}
avatarhash={message.avatarhash}
/>
</Grid>
<Grid xs={10}>
<Typography variant="body1" color="text">
{message.body_md}
</Typography>
</Grid>
</ContainerWithHeader>
</Grid>
);
};
Loading

0 comments on commit e490200

Please sign in to comment.