mirror of
https://github.com/owncast/owncast.git
synced 2024-11-24 21:59:43 +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 { Menu, Dropdown, Button, Space } from 'antd';
|
||||||
import { DownOutlined } from '@ant-design/icons';
|
import { DownOutlined } from '@ant-design/icons';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { chatVisibilityAtom } from '../../stores/ClientConfigStore';
|
import { chatVisibilityAtom, chatDisplayNameAtom } from '../../stores/ClientConfigStore';
|
||||||
import { ChatState, ChatVisibilityState } from '../../../interfaces/application-state';
|
import { ChatState, ChatVisibilityState } from '../../../interfaces/application-state';
|
||||||
import s from './UserDropdown.module.scss';
|
import s from './UserDropdown.module.scss';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
username: string;
|
username?: string;
|
||||||
chatState: ChatState;
|
chatState: ChatState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UserDropdown({ username = 'test-user', chatState }: Props) {
|
export default function UserDropdown({ username: defaultUsername, chatState }: Props) {
|
||||||
const chatEnabled = chatState !== ChatState.NotAvailable;
|
|
||||||
const [chatVisibility, setChatVisibility] =
|
const [chatVisibility, setChatVisibility] =
|
||||||
useRecoilState<ChatVisibilityState>(chatVisibilityAtom);
|
useRecoilState<ChatVisibilityState>(chatVisibilityAtom);
|
||||||
|
const username = defaultUsername || useRecoilValue(chatDisplayNameAtom);
|
||||||
|
|
||||||
const toggleChatVisibility = () => {
|
const toggleChatVisibility = () => {
|
||||||
if (chatVisibility === ChatVisibilityState.Hidden) {
|
if (chatVisibility === ChatVisibilityState.Hidden) {
|
||||||
|
@ -27,7 +27,7 @@ export default function UserDropdown({ username = 'test-user', chatState }: Prop
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item key="0">Change name</Menu.Item>
|
<Menu.Item key="0">Change name</Menu.Item>
|
||||||
<Menu.Item key="1">Authenticate</Menu.Item>
|
<Menu.Item key="1">Authenticate</Menu.Item>
|
||||||
{chatEnabled && (
|
{chatState === ChatState.Available && (
|
||||||
<Menu.Item key="3" onClick={() => toggleChatVisibility()}>
|
<Menu.Item key="3" onClick={() => toggleChatVisibility()}>
|
||||||
Toggle chat
|
Toggle chat
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
@ -48,3 +48,7 @@ export default function UserDropdown({ username = 'test-user', chatState }: Prop
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserDropdown.defaultProps = {
|
||||||
|
username: undefined,
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Layout } from 'antd';
|
import { Layout } from 'antd';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { ServerStatusStore } from '../stores/ServerStatusStore';
|
|
||||||
import { ClientConfigStore, clientConfigStateAtom } from '../stores/ClientConfigStore';
|
import { ClientConfigStore, clientConfigStateAtom } from '../stores/ClientConfigStore';
|
||||||
import { Content, Header } from '../ui';
|
import { Content, Header } from '../ui';
|
||||||
import { ClientConfig } from '../../interfaces/client-config.model';
|
import { ClientConfig } from '../../interfaces/client-config.model';
|
||||||
|
@ -11,7 +10,6 @@ function Main() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ServerStatusStore />
|
|
||||||
<ClientConfigStore />
|
<ClientConfigStore />
|
||||||
<Layout>
|
<Layout>
|
||||||
<Header name={title || name} />
|
<Header name={title || name} />
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
/* eslint-disable no-case-declarations */
|
/* eslint-disable no-case-declarations */
|
||||||
import { useEffect, useLayoutEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { atom, useRecoilState, useSetRecoilState } from 'recoil';
|
import { atom, useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
import { makeEmptyClientConfig, ClientConfig } from '../../interfaces/client-config.model';
|
import { makeEmptyClientConfig, ClientConfig } from '../../interfaces/client-config.model';
|
||||||
import ClientConfigService from '../../services/client-config-service';
|
import ClientConfigService from '../../services/client-config-service';
|
||||||
import ChatService from '../../services/chat-service';
|
import ChatService from '../../services/chat-service';
|
||||||
import WebsocketService from '../../services/websocket-service';
|
import WebsocketService from '../../services/websocket-service';
|
||||||
import { ChatMessage } from '../../interfaces/chat-message.model';
|
import { ChatMessage } from '../../interfaces/chat-message.model';
|
||||||
|
import { ServerStatus, makeEmptyServerStatus } from '../../interfaces/server-status.model';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
ChatState,
|
ChatState,
|
||||||
|
@ -22,6 +24,14 @@ import {
|
||||||
} from '../../interfaces/socket-events';
|
} from '../../interfaces/socket-events';
|
||||||
import handleConnectedClientInfoMessage from './eventhandlers/connectedclientinfo';
|
import handleConnectedClientInfoMessage from './eventhandlers/connectedclientinfo';
|
||||||
import handleChatMessage from './eventhandlers/handleChatMessage';
|
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.
|
// The config that comes from the API.
|
||||||
export const clientConfigStateAtom = atom({
|
export const clientConfigStateAtom = atom({
|
||||||
|
@ -71,6 +81,7 @@ export const websocketServiceAtom = atom<WebsocketService>({
|
||||||
|
|
||||||
export function ClientConfigStore() {
|
export function ClientConfigStore() {
|
||||||
const setClientConfig = useSetRecoilState<ClientConfig>(clientConfigStateAtom);
|
const setClientConfig = useSetRecoilState<ClientConfig>(clientConfigStateAtom);
|
||||||
|
const setServerStatus = useSetRecoilState<ServerStatus>(serverStatusState);
|
||||||
const setChatVisibility = useSetRecoilState<ChatVisibilityState>(chatVisibilityAtom);
|
const setChatVisibility = useSetRecoilState<ChatVisibilityState>(chatVisibilityAtom);
|
||||||
const setChatState = useSetRecoilState<ChatState>(chatStateAtom);
|
const setChatState = useSetRecoilState<ChatState>(chatStateAtom);
|
||||||
const [chatMessages, setChatMessages] = useRecoilState<ChatMessage[]>(chatMessagesAtom);
|
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) => {
|
const handleUserRegistration = async (optionalDisplayName?: string) => {
|
||||||
try {
|
try {
|
||||||
setAppState(AppState.Registering);
|
setAppState(AppState.Registering);
|
||||||
|
@ -140,7 +167,6 @@ export function ClientConfigStore() {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`ChatService -> startChat() ERROR: \n${error}`);
|
console.error(`ChatService -> startChat() ERROR: \n${error}`);
|
||||||
}
|
}
|
||||||
setChatState(ChatState.Available);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -148,7 +174,14 @@ export function ClientConfigStore() {
|
||||||
handleUserRegistration();
|
handleUserRegistration();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useEffect(() => {
|
||||||
|
setInterval(() => {
|
||||||
|
updateServerStatus();
|
||||||
|
}, 5000);
|
||||||
|
updateServerStatus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -159,6 +192,7 @@ export function ClientConfigStore() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updatedChatState = getChatState(appState);
|
const updatedChatState = getChatState(appState);
|
||||||
|
console.log('updatedChatState', updatedChatState);
|
||||||
setChatState(updatedChatState);
|
setChatState(updatedChatState);
|
||||||
const updatedChatVisibility = getChatVisibilityState(appState);
|
const updatedChatVisibility = getChatVisibilityState(appState);
|
||||||
console.log(
|
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,
|
clientConfigStateAtom,
|
||||||
chatMessagesAtom,
|
chatMessagesAtom,
|
||||||
chatStateAtom,
|
chatStateAtom,
|
||||||
|
serverStatusState,
|
||||||
} from '../../stores/ClientConfigStore';
|
} from '../../stores/ClientConfigStore';
|
||||||
import { serverStatusState } from '../../stores/ServerStatusStore';
|
|
||||||
import { ClientConfig } from '../../../interfaces/client-config.model';
|
import { ClientConfig } from '../../../interfaces/client-config.model';
|
||||||
import CustomPageContent from '../../CustomPageContent';
|
import CustomPageContent from '../../CustomPageContent';
|
||||||
import OwncastPlayer from '../../video/OwncastPlayer';
|
import OwncastPlayer from '../../video/OwncastPlayer';
|
||||||
|
@ -30,7 +30,7 @@ const { Content } = Layout;
|
||||||
export default function ContentComponent() {
|
export default function ContentComponent() {
|
||||||
const status = useRecoilValue<ServerStatus>(serverStatusState);
|
const status = useRecoilValue<ServerStatus>(serverStatusState);
|
||||||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
||||||
const chatOpen = useRecoilValue<ChatVisibilityState>(chatVisibilityAtom);
|
const chatVisibility = useRecoilValue<ChatVisibilityState>(chatVisibilityAtom);
|
||||||
const messages = useRecoilValue<ChatMessage[]>(chatMessagesAtom);
|
const messages = useRecoilValue<ChatMessage[]>(chatMessagesAtom);
|
||||||
const chatState = useRecoilValue<ChatState>(chatStateAtom);
|
const chatState = useRecoilValue<ChatState>(chatStateAtom);
|
||||||
|
|
||||||
|
@ -41,6 +41,9 @@ export default function ContentComponent() {
|
||||||
|
|
||||||
const total = 0;
|
const total = 0;
|
||||||
|
|
||||||
|
const isShowingChatColumn =
|
||||||
|
chatState === ChatState.Available && chatVisibility === ChatVisibilityState.Visible;
|
||||||
|
|
||||||
// This is example content. It should be removed.
|
// This is example content. It should be removed.
|
||||||
const externalActions = [
|
const externalActions = [
|
||||||
{
|
{
|
||||||
|
@ -58,7 +61,7 @@ export default function ContentComponent() {
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Content className={`${s.root}`} data-columns={chatOpen ? 2 : 1}>
|
<Content className={`${s.root}`} data-columns={isShowingChatColumn ? 2 : 1}>
|
||||||
<div className={`${s.leftCol}`}>
|
<div className={`${s.leftCol}`}>
|
||||||
<OwncastPlayer source="/hls/stream.m3u8" online={online} />
|
<OwncastPlayer source="/hls/stream.m3u8" online={online} />
|
||||||
<Statusbar
|
<Statusbar
|
||||||
|
@ -72,6 +75,7 @@ export default function ContentComponent() {
|
||||||
<Button>Follow</Button>
|
<Button>Follow</Button>
|
||||||
<Button>Notify</Button>
|
<Button>Notify</Button>
|
||||||
</ActionButtonRow>
|
</ActionButtonRow>
|
||||||
|
|
||||||
<div className={`${s.lowerRow}`}>
|
<div className={`${s.lowerRow}`}>
|
||||||
<Tabs defaultActiveKey="1" type="card">
|
<Tabs defaultActiveKey="1" type="card">
|
||||||
<TabPane tab="About" key="1" className={`${s.pageContentSection}`}>
|
<TabPane tab="About" key="1" className={`${s.pageContentSection}`}>
|
||||||
|
@ -82,16 +86,17 @@ export default function ContentComponent() {
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{chatOpen && (
|
{chatVisibility && (
|
||||||
<div className={`${s.mobileChat}`}>
|
<div className={`${s.mobileChat}`}>
|
||||||
<ChatContainer messages={messages} state={chatState} />
|
<ChatContainer messages={messages} state={chatState} />
|
||||||
<ChatTextField />
|
<ChatTextField />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Footer version={version} />
|
<Footer version={version} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{chatOpen && <Sidebar />}
|
{isShowingChatColumn && <Sidebar />}
|
||||||
</Content>
|
</Content>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,5 +9,5 @@ interface Props {
|
||||||
export default function FooterComponent(props: Props) {
|
export default function FooterComponent(props: Props) {
|
||||||
const { version } = 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 { Layout } from 'antd';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
import { ChatState } from '../../../interfaces/application-state';
|
import { ChatState } from '../../../interfaces/application-state';
|
||||||
import { OwncastLogo, UserDropdown } from '../../common';
|
import { OwncastLogo, UserDropdown } from '../../common';
|
||||||
|
import { chatStateAtom } from '../../stores/ClientConfigStore';
|
||||||
import s from './Header.module.scss';
|
import s from './Header.module.scss';
|
||||||
|
|
||||||
const { Header } = Layout;
|
const { Header } = Layout;
|
||||||
|
@ -10,13 +12,15 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HeaderComponent({ name = 'Your stream title' }: Props) {
|
export default function HeaderComponent({ name = 'Your stream title' }: Props) {
|
||||||
|
const chatState = useRecoilValue<ChatState>(chatStateAtom);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header className={`${s.header}`}>
|
<Header className={`${s.header}`}>
|
||||||
<div className={`${s.logo}`}>
|
<div className={`${s.logo}`}>
|
||||||
<OwncastLogo variant="contrast" />
|
<OwncastLogo variant="contrast" />
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
</div>
|
</div>
|
||||||
<UserDropdown username="fillmein" chatState={ChatState.Available} />
|
<UserDropdown chatState={chatState} />
|
||||||
</Header>
|
</Header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ export default function Modal(props: Props) {
|
||||||
height: '80vh',
|
height: '80vh',
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(url);
|
|
||||||
const iframe = url && (
|
const iframe = url && (
|
||||||
<iframe
|
<iframe
|
||||||
title={title}
|
title={title}
|
||||||
|
|
|
@ -13,10 +13,10 @@ export enum ChatVisibilityState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ChatState {
|
export enum ChatState {
|
||||||
Available, // Normal state. Chat can be visible and used.
|
Available = 'Available', // Normal state. Chat can be visible and used.
|
||||||
NotAvailable, // Chat features are not available.
|
NotAvailable = 'NotAvailable', // Chat features are not available.
|
||||||
Loading, // Chat is connecting and loading history.
|
Loading = 'Loading', // Chat is connecting and loading history.
|
||||||
Offline, // Chat is offline/disconnected for some reason but is visible.
|
Offline = 'Offline', // Chat is offline/disconnected for some reason but is visible.
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum VideoState {
|
export enum VideoState {
|
||||||
|
|
Loading…
Reference in a new issue