mirror of
https://github.com/owncast/owncast.git
synced 2024-11-21 20:28:15 +03:00
More accurately hide/show and track chat state
This commit is contained in:
parent
4b2742739a
commit
e0f8a1f702
9 changed files with 67 additions and 56 deletions
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ export default function Modal(props: Props) {
|
|||
height: '80vh',
|
||||
};
|
||||
|
||||
console.log(url);
|
||||
const iframe = url && (
|
||||
<iframe
|
||||
title={title}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue