Polish up the initial loading experience

This commit is contained in:
Gabe Kangas 2022-09-10 18:08:51 -07:00
parent 5b29abd42d
commit 8d02f4068d
No known key found for this signature in database
GPG Key ID: 9A56337728BC81EA
4 changed files with 90 additions and 76 deletions

View File

@ -1,4 +1,4 @@
import { FC, useEffect } from 'react';
import { FC, useEffect, useState } from 'react';
import { atom, selector, useRecoilState, useSetRecoilState } from 'recoil';
import { useMachine } from '@xstate/react';
import { makeEmptyClientConfig, ClientConfig } from '../../interfaces/client-config.model';
@ -29,6 +29,8 @@ import { DisplayableError } from '../../types/displayable-error';
const SERVER_STATUS_POLL_DURATION = 5000;
const ACCESS_TOKEN_KEY = 'accessToken';
let serverStatusRefreshPoll: ReturnType<typeof setInterval>;
// Server status is what gets updated such as viewer count, durations,
// stream title, online/offline state, etc.
export const serverStatusState = atom<ServerStatus>({
@ -187,6 +189,8 @@ export const ClientConfigStore: FC = () => {
const setGlobalFatalErrorMessage = useSetRecoilState<DisplayableError>(fatalErrorStateAtom);
const setWebsocketService = useSetRecoilState<WebsocketService>(websocketServiceAtom);
const [hiddenMessageIds, setHiddenMessageIds] = useRecoilState<string[]>(removedMessageIdsAtom);
const [hasLoadedStatus, setHasLoadedStatus] = useState(false);
const [hasLoadedConfig, setHasLoadedConfig] = useState(false);
let ws: WebsocketService;
@ -205,12 +209,12 @@ export const ClientConfigStore: FC = () => {
try {
const config = await ClientConfigService.getConfig();
setClientConfig(config);
sendEvent('LOADED');
setGlobalFatalErrorMessage(null);
setHasLoadedConfig(true);
} catch (error) {
setGlobalFatalError(
'Unable to reach Owncast server',
`Owncast cannot launch. Please make sure the Owncast server is running. ${error}`,
`Owncast cannot launch. Please make sure the Owncast server is running.`,
);
console.error(`ClientConfigService -> getConfig() ERROR: \n${error}`);
}
@ -220,6 +224,7 @@ export const ClientConfigStore: FC = () => {
try {
const status = await ServerStatusService.getStatus();
setServerStatus(status);
setHasLoadedStatus(true);
const { serverTime } = status;
const clockSkew = new Date(serverTime).getTime() - Date.now();
@ -332,12 +337,10 @@ export const ClientConfigStore: FC = () => {
};
const startChat = async () => {
sendEvent(AppStateEvent.Loading);
try {
ws = new WebsocketService(accessToken, '/ws');
ws.handleMessage = handleMessage;
setWebsocketService(ws);
sendEvent(AppStateEvent.Loaded);
} catch (error) {
console.error(`ChatService -> startChat() ERROR: \n${error}`);
}
@ -366,11 +369,19 @@ export const ClientConfigStore: FC = () => {
}
}, []);
useEffect(() => {
if (hasLoadedStatus && hasLoadedConfig) {
sendEvent(AppStateEvent.Loaded);
}
}, [hasLoadedStatus, hasLoadedConfig]);
useEffect(() => {
updateClientConfig();
handleUserRegistration();
updateServerStatus();
setInterval(() => {
clearInterval(serverStatusRefreshPoll);
serverStatusRefreshPoll = setInterval(() => {
updateServerStatus();
}, SERVER_STATUS_POLL_DURATION);
}, [appState]);

View File

@ -170,79 +170,82 @@ export const Content: FC = () => {
window.addEventListener('resize', checkIfMobile);
}, []);
let offlineMessageBody =
!appState.appLoading && 'Please follow and ask to get notified when the stream is live.';
if (offlineMessage && !appState.appLoading) {
offlineMessageBody = offlineMessage;
}
const offlineTitle = !appState.appLoading && `${name} is currently offline`;
return (
<div>
<AntContent className={styles.root}>
<div className={styles.leftContent}>
<Spin className={styles.loadingSpinner} size="large" spinning={appState.appLoading} />
<Spin className={styles.loadingSpinner} size="large" spinning={appState.appLoading}>
<AntContent className={styles.root}>
<div className={styles.leftContent}>
<div className={styles.topSection}>
{online && <OwncastPlayer source="/hls/stream.m3u8" online={online} />}
{!online && !appState.appLoading && (
<OfflineBanner title={offlineTitle} text={offlineMessageBody} />
)}
<Statusbar
online={online}
lastConnectTime={lastConnectTime}
lastDisconnectTime={lastDisconnectTime}
viewerCount={viewerCount}
/>
</div>
<div className={styles.midSection}>
<div className={styles.buttonsLogoTitleSection}>
<ActionButtonRow>
{externalActionButtons}
<FollowButton size="small" />
<NotifyReminderPopup
visible={showNotifyReminder}
notificationClicked={() => setShowNotifyPopup(true)}
notificationClosed={() => disableNotifyReminderPopup()}
>
<NotifyButton onClick={() => setShowNotifyPopup(true)} />
</NotifyReminderPopup>
</ActionButtonRow>
<div className={styles.topSection}>
{online && <OwncastPlayer source="/hls/stream.m3u8" online={online} />}
{!online && (
<OfflineBanner
<Modal
title="Notify"
visible={showNotifyPopup}
afterClose={() => disableNotifyReminderPopup()}
handleCancel={() => disableNotifyReminderPopup()}
>
<BrowserNotifyModal />
</Modal>
</div>
</div>
{isMobile && isChatVisible ? (
<MobileContent
name={name}
text={
offlineMessage || 'Please follow and ask to get notified when the stream is live.'
}
streamTitle={streamTitle}
summary={summary}
tags={tags}
socialHandles={socialHandles}
extraPageContent={extraPageContent}
messages={messages}
chatDisplayName={chatDisplayName}
chatUserId={chatUserId}
/>
) : (
<DesktopContent
name={name}
streamTitle={streamTitle}
summary={summary}
tags={tags}
socialHandles={socialHandles}
extraPageContent={extraPageContent}
/>
)}
<Statusbar
online={online}
lastConnectTime={lastConnectTime}
lastDisconnectTime={lastDisconnectTime}
viewerCount={viewerCount}
/>
</div>
<div className={styles.midSection}>
<div className={styles.buttonsLogoTitleSection}>
<ActionButtonRow>
{externalActionButtons}
<FollowButton size="small" />
<NotifyReminderPopup
visible={showNotifyReminder}
notificationClicked={() => setShowNotifyPopup(true)}
notificationClosed={() => disableNotifyReminderPopup()}
>
<NotifyButton onClick={() => setShowNotifyPopup(true)} />
</NotifyReminderPopup>
</ActionButtonRow>
<Modal
title="Notify"
visible={showNotifyPopup}
afterClose={() => disableNotifyReminderPopup()}
handleCancel={() => disableNotifyReminderPopup()}
>
<BrowserNotifyModal />
</Modal>
</div>
</div>
{isMobile && isChatVisible ? (
<MobileContent
name={name}
streamTitle={streamTitle}
summary={summary}
tags={tags}
socialHandles={socialHandles}
extraPageContent={extraPageContent}
messages={messages}
chatDisplayName={chatDisplayName}
chatUserId={chatUserId}
/>
) : (
<DesktopContent
name={name}
streamTitle={streamTitle}
summary={summary}
tags={tags}
socialHandles={socialHandles}
extraPageContent={extraPageContent}
/>
)}
</div>
{isChatVisible && !isMobile && <Sidebar />}
</AntContent>
{(!isMobile || !isChatVisible) && <Footer version={version} />}
{isChatVisible && !isMobile && <Sidebar />}
</AntContent>
{(!isMobile || !isChatVisible) && <Footer version={version} />}
</Spin>
</div>
);
};

View File

@ -4,14 +4,14 @@ import { FC } from 'react';
import styles from './OfflineBanner.module.scss';
export type OfflineBannerProps = {
name: string;
title: string;
text: string;
};
export const OfflineBanner: FC<OfflineBannerProps> = ({ name, text }) => (
export const OfflineBanner: FC<OfflineBannerProps> = ({ title, text }) => (
<div className={styles.outerContainer}>
<div className={styles.innerContainer}>
<div className={styles.header}>{name} is currently offline.</div>
<div className={styles.header}>{title}</div>
<Divider />
<div>{text}</div>

View File

@ -26,7 +26,7 @@ export default function VideoEmbed() {
<ClientConfigStore />
<div className="video-embed">
{online && <OwncastPlayer source="/hls/stream.m3u8" online={online} />}
{!online && <OfflineBanner name={name} text="Stream is offline text goes here." />}{' '}
{!online && <OfflineBanner title={name} text="Stream is offline text goes here." />}{' '}
<Statusbar
online={online}
lastConnectTime={lastConnectTime}