More accurately hide/show and track chat state

This commit is contained in:
Gabe Kangas 2022-05-13 14:44:16 -07:00
parent 4b2742739a
commit e0f8a1f702
No known key found for this signature in database
GPG key ID: 9A56337728BC81EA
9 changed files with 67 additions and 56 deletions

View file

@ -1,19 +1,19 @@
import { Menu, Dropdown, Button, Space } from 'antd';
import { DownOutlined } from '@ant-design/icons';
import { useRecoilState } from 'recoil';
import { chatVisibilityAtom } from '../../stores/ClientConfigStore';
import { useRecoilState, useRecoilValue } from 'recoil';
import { chatVisibilityAtom, chatDisplayNameAtom } from '../../stores/ClientConfigStore';
import { ChatState, ChatVisibilityState } from '../../../interfaces/application-state';
import s from './UserDropdown.module.scss';
interface Props {
username: string;
username?: string;
chatState: ChatState;
}
export default function UserDropdown({ username = 'test-user', chatState }: Props) {
const chatEnabled = chatState !== ChatState.NotAvailable;
export default function UserDropdown({ username: defaultUsername, chatState }: Props) {
const [chatVisibility, setChatVisibility] =
useRecoilState<ChatVisibilityState>(chatVisibilityAtom);
const username = defaultUsername || useRecoilValue(chatDisplayNameAtom);
const toggleChatVisibility = () => {
if (chatVisibility === ChatVisibilityState.Hidden) {
@ -27,7 +27,7 @@ export default function UserDropdown({ username = 'test-user', chatState }: Prop
<Menu>
<Menu.Item key="0">Change name</Menu.Item>
<Menu.Item key="1">Authenticate</Menu.Item>
{chatEnabled && (
{chatState === ChatState.Available && (
<Menu.Item key="3" onClick={() => toggleChatVisibility()}>
Toggle chat
</Menu.Item>
@ -48,3 +48,7 @@ export default function UserDropdown({ username = 'test-user', chatState }: Prop
</div>
);
}
UserDropdown.defaultProps = {
username: undefined,
};

View file

@ -1,6 +1,5 @@
import { Layout } from 'antd';
import { useRecoilValue } from 'recoil';
import { ServerStatusStore } from '../stores/ServerStatusStore';
import { ClientConfigStore, clientConfigStateAtom } from '../stores/ClientConfigStore';
import { Content, Header } from '../ui';
import { ClientConfig } from '../../interfaces/client-config.model';
@ -11,7 +10,6 @@ function Main() {
return (
<>
<ServerStatusStore />
<ClientConfigStore />
<Layout>
<Header name={title || name} />

View file

@ -1,11 +1,13 @@
/* eslint-disable no-case-declarations */
import { useEffect, useLayoutEffect } from 'react';
import { useEffect } from 'react';
import { atom, useRecoilState, useSetRecoilState } from 'recoil';
import { makeEmptyClientConfig, ClientConfig } from '../../interfaces/client-config.model';
import ClientConfigService from '../../services/client-config-service';
import ChatService from '../../services/chat-service';
import WebsocketService from '../../services/websocket-service';
import { ChatMessage } from '../../interfaces/chat-message.model';
import { ServerStatus, makeEmptyServerStatus } from '../../interfaces/server-status.model';
import {
AppState,
ChatState,
@ -22,6 +24,14 @@ import {
} from '../../interfaces/socket-events';
import handleConnectedClientInfoMessage from './eventhandlers/connectedclientinfo';
import handleChatMessage from './eventhandlers/handleChatMessage';
import ServerStatusService from '../../services/status-service';
// Server status is what gets updated such as viewer count, durations,
// stream title, online/offline state, etc.
export const serverStatusState = atom<ServerStatus>({
key: 'serverStatusState',
default: makeEmptyServerStatus(),
});
// The config that comes from the API.
export const clientConfigStateAtom = atom({
@ -71,6 +81,7 @@ export const websocketServiceAtom = atom<WebsocketService>({
export function ClientConfigStore() {
const setClientConfig = useSetRecoilState<ClientConfig>(clientConfigStateAtom);
const setServerStatus = useSetRecoilState<ServerStatus>(serverStatusState);
const setChatVisibility = useSetRecoilState<ChatVisibilityState>(chatVisibilityAtom);
const setChatState = useSetRecoilState<ChatState>(chatStateAtom);
const [chatMessages, setChatMessages] = useRecoilState<ChatMessage[]>(chatMessagesAtom);
@ -90,6 +101,22 @@ export function ClientConfigStore() {
}
};
const updateServerStatus = async () => {
try {
const status = await ServerStatusService.getStatus();
setServerStatus(status);
if (status.online) {
setAppState(AppState.Online);
} else {
setAppState(AppState.Offline);
}
return status;
} catch (error) {
console.error(`serverStatusState -> getStatus() ERROR: \n${error}`);
return null;
}
};
const handleUserRegistration = async (optionalDisplayName?: string) => {
try {
setAppState(AppState.Registering);
@ -140,7 +167,6 @@ export function ClientConfigStore() {
} catch (error) {
console.error(`ChatService -> startChat() ERROR: \n${error}`);
}
setChatState(ChatState.Available);
};
useEffect(() => {
@ -148,7 +174,14 @@ export function ClientConfigStore() {
handleUserRegistration();
}, []);
useLayoutEffect(() => {
useEffect(() => {
setInterval(() => {
updateServerStatus();
}, 5000);
updateServerStatus();
}, []);
useEffect(() => {
if (!accessToken) {
return;
}
@ -159,6 +192,7 @@ export function ClientConfigStore() {
useEffect(() => {
const updatedChatState = getChatState(appState);
console.log('updatedChatState', updatedChatState);
setChatState(updatedChatState);
const updatedChatVisibility = getChatVisibilityState(appState);
console.log(

View file

@ -1,33 +0,0 @@
import { useEffect } from 'react';
import { atom, useRecoilState } from 'recoil';
import { ServerStatus, makeEmptyServerStatus } from '../../interfaces/server-status.model';
import ServerStatusService from '../../services/status-service';
export const serverStatusState = atom({
key: 'serverStatusState',
default: makeEmptyServerStatus(),
});
export function ServerStatusStore() {
const [, setServerStatus] = useRecoilState<ServerStatus>(serverStatusState);
const updateServerStatus = async () => {
try {
const status = await ServerStatusService.getStatus();
setServerStatus(status);
return status;
} catch (error) {
console.error(`serverStatusState -> getStatus() ERROR: \n${error}`);
return null;
}
};
useEffect(() => {
setInterval(() => {
updateServerStatus();
}, 5000);
updateServerStatus();
}, []);
return null;
}

View file

@ -5,8 +5,8 @@ import {
clientConfigStateAtom,
chatMessagesAtom,
chatStateAtom,
serverStatusState,
} from '../../stores/ClientConfigStore';
import { serverStatusState } from '../../stores/ServerStatusStore';
import { ClientConfig } from '../../../interfaces/client-config.model';
import CustomPageContent from '../../CustomPageContent';
import OwncastPlayer from '../../video/OwncastPlayer';
@ -30,7 +30,7 @@ const { Content } = Layout;
export default function ContentComponent() {
const status = useRecoilValue<ServerStatus>(serverStatusState);
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
const chatOpen = useRecoilValue<ChatVisibilityState>(chatVisibilityAtom);
const chatVisibility = useRecoilValue<ChatVisibilityState>(chatVisibilityAtom);
const messages = useRecoilValue<ChatMessage[]>(chatMessagesAtom);
const chatState = useRecoilValue<ChatState>(chatStateAtom);
@ -41,6 +41,9 @@ export default function ContentComponent() {
const total = 0;
const isShowingChatColumn =
chatState === ChatState.Available && chatVisibility === ChatVisibilityState.Visible;
// This is example content. It should be removed.
const externalActions = [
{
@ -58,7 +61,7 @@ export default function ContentComponent() {
));
return (
<Content className={`${s.root}`} data-columns={chatOpen ? 2 : 1}>
<Content className={`${s.root}`} data-columns={isShowingChatColumn ? 2 : 1}>
<div className={`${s.leftCol}`}>
<OwncastPlayer source="/hls/stream.m3u8" online={online} />
<Statusbar
@ -72,6 +75,7 @@ export default function ContentComponent() {
<Button>Follow</Button>
<Button>Notify</Button>
</ActionButtonRow>
<div className={`${s.lowerRow}`}>
<Tabs defaultActiveKey="1" type="card">
<TabPane tab="About" key="1" className={`${s.pageContentSection}`}>
@ -82,16 +86,17 @@ export default function ContentComponent() {
</TabPane>
</Tabs>
{chatOpen && (
{chatVisibility && (
<div className={`${s.mobileChat}`}>
<ChatContainer messages={messages} state={chatState} />
<ChatTextField />
</div>
)}
<Footer version={version} />
</div>
</div>
{chatOpen && <Sidebar />}
{isShowingChatColumn && <Sidebar />}
</Content>
);
}

View file

@ -9,5 +9,5 @@ interface Props {
export default function FooterComponent(props: Props) {
const { version } = props;
return <Footer style={{ textAlign: 'center', height: '64px' }}>Footer: Owncast {version}</Footer>;
return <Footer style={{ textAlign: 'center', height: '64px' }}>Owncast {version}</Footer>;
}

View file

@ -1,6 +1,8 @@
import { Layout } from 'antd';
import { useRecoilValue } from 'recoil';
import { ChatState } from '../../../interfaces/application-state';
import { OwncastLogo, UserDropdown } from '../../common';
import { chatStateAtom } from '../../stores/ClientConfigStore';
import s from './Header.module.scss';
const { Header } = Layout;
@ -10,13 +12,15 @@ interface Props {
}
export default function HeaderComponent({ name = 'Your stream title' }: Props) {
const chatState = useRecoilValue<ChatState>(chatStateAtom);
return (
<Header className={`${s.header}`}>
<div className={`${s.logo}`}>
<OwncastLogo variant="contrast" />
<span>{name}</span>
</div>
<UserDropdown username="fillmein" chatState={ChatState.Available} />
<UserDropdown chatState={chatState} />
</Header>
);
}

View file

@ -21,7 +21,6 @@ export default function Modal(props: Props) {
height: '80vh',
};
console.log(url);
const iframe = url && (
<iframe
title={title}

View file

@ -13,10 +13,10 @@ export enum ChatVisibilityState {
}
export enum ChatState {
Available, // Normal state. Chat can be visible and used.
NotAvailable, // Chat features are not available.
Loading, // Chat is connecting and loading history.
Offline, // Chat is offline/disconnected for some reason but is visible.
Available = 'Available', // Normal state. Chat can be visible and used.
NotAvailable = 'NotAvailable', // Chat features are not available.
Loading = 'Loading', // Chat is connecting and loading history.
Offline = 'Offline', // Chat is offline/disconnected for some reason but is visible.
}
export enum VideoState {