Fix missing live duration string. Closes #144
This commit is contained in:
@@ -7,6 +7,7 @@ import SocialIcon from './components/social.js';
|
|||||||
import UsernameForm from './components/chat/username.js';
|
import UsernameForm from './components/chat/username.js';
|
||||||
import Chat from './components/chat/chat.js';
|
import Chat from './components/chat/chat.js';
|
||||||
import Websocket from './utils/websocket.js';
|
import Websocket from './utils/websocket.js';
|
||||||
|
import { secondsToHMMSS } from './utils/helpers.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addNewlines,
|
addNewlines,
|
||||||
@@ -46,7 +47,8 @@ export default class App extends Component {
|
|||||||
chatEnabled: false, // chat input box state
|
chatEnabled: false, // chat input box state
|
||||||
username: getLocalStorage(KEY_USERNAME) || generateUsername(),
|
username: getLocalStorage(KEY_USERNAME) || generateUsername(),
|
||||||
userAvatarImage:
|
userAvatarImage:
|
||||||
getLocalStorage(KEY_AVATAR) || generateAvatar(`${this.username}${Date.now()}`),
|
getLocalStorage(KEY_AVATAR) ||
|
||||||
|
generateAvatar(`${this.username}${Date.now()}`),
|
||||||
|
|
||||||
configData: {},
|
configData: {},
|
||||||
extraUserContent: '',
|
extraUserContent: '',
|
||||||
@@ -80,6 +82,7 @@ export default class App extends Component {
|
|||||||
this.handleOfflineMode = this.handleOfflineMode.bind(this);
|
this.handleOfflineMode = this.handleOfflineMode.bind(this);
|
||||||
this.handleOnlineMode = this.handleOnlineMode.bind(this);
|
this.handleOnlineMode = this.handleOnlineMode.bind(this);
|
||||||
this.disableChatInput = this.disableChatInput.bind(this);
|
this.disableChatInput = this.disableChatInput.bind(this);
|
||||||
|
this.setCurrentStreamDuration = this.setCurrentStreamDuration.bind(this);
|
||||||
|
|
||||||
// player events
|
// player events
|
||||||
this.handlePlayerReady = this.handlePlayerReady.bind(this);
|
this.handlePlayerReady = this.handlePlayerReady.bind(this);
|
||||||
@@ -120,16 +123,16 @@ export default class App extends Component {
|
|||||||
// fetch /config data
|
// fetch /config data
|
||||||
getConfig() {
|
getConfig() {
|
||||||
fetch(URL_CONFIG)
|
fetch(URL_CONFIG)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Network response was not ok ${response.ok}`);
|
throw new Error(`Network response was not ok ${response.ok}`);
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(json => {
|
.then((json) => {
|
||||||
this.setConfigData(json);
|
this.setConfigData(json);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
this.handleNetworkingError(`Fetch config: ${error}`);
|
this.handleNetworkingError(`Fetch config: ${error}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -137,16 +140,16 @@ export default class App extends Component {
|
|||||||
// fetch stream status
|
// fetch stream status
|
||||||
getStreamStatus() {
|
getStreamStatus() {
|
||||||
fetch(URL_STATUS)
|
fetch(URL_STATUS)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Network response was not ok ${response.ok}`);
|
throw new Error(`Network response was not ok ${response.ok}`);
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(json => {
|
.then((json) => {
|
||||||
this.updateStreamStatus(json);
|
this.updateStreamStatus(json);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
this.handleOfflineMode();
|
this.handleOfflineMode();
|
||||||
this.handleNetworkingError(`Stream status: ${error}`);
|
this.handleNetworkingError(`Stream status: ${error}`);
|
||||||
});
|
});
|
||||||
@@ -155,19 +158,18 @@ export default class App extends Component {
|
|||||||
// fetch content.md
|
// fetch content.md
|
||||||
getExtraUserContent(path) {
|
getExtraUserContent(path) {
|
||||||
fetch(path)
|
fetch(path)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Network response was not ok ${response.ok}`);
|
throw new Error(`Network response was not ok ${response.ok}`);
|
||||||
}
|
}
|
||||||
return response.text();
|
return response.text();
|
||||||
})
|
})
|
||||||
.then(text => {
|
.then((text) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
extraUserContent: new showdown.Converter().makeHtml(text),
|
extraUserContent: new showdown.Converter().makeHtml(text),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfigData(data = {}) {
|
setConfigData(data = {}) {
|
||||||
@@ -198,6 +200,7 @@ export default class App extends Component {
|
|||||||
sessionMaxViewerCount,
|
sessionMaxViewerCount,
|
||||||
overallMaxViewerCount,
|
overallMaxViewerCount,
|
||||||
online,
|
online,
|
||||||
|
lastConnectTime,
|
||||||
} = status;
|
} = status;
|
||||||
|
|
||||||
this.lastDisconnectTime = status.lastDisconnectTime;
|
this.lastDisconnectTime = status.lastDisconnectTime;
|
||||||
@@ -219,6 +222,7 @@ export default class App extends Component {
|
|||||||
viewerCount,
|
viewerCount,
|
||||||
sessionMaxViewerCount,
|
sessionMaxViewerCount,
|
||||||
overallMaxViewerCount,
|
overallMaxViewerCount,
|
||||||
|
lastConnectTime,
|
||||||
streamOnline: online,
|
streamOnline: online,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -233,7 +237,6 @@ export default class App extends Component {
|
|||||||
// do something?
|
// do something?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// likely called some time after stream status has gone offline.
|
// likely called some time after stream status has gone offline.
|
||||||
// basically hide video and show underlying "poster"
|
// basically hide video and show underlying "poster"
|
||||||
handlePlayerEnded() {
|
handlePlayerEnded() {
|
||||||
@@ -251,8 +254,10 @@ export default class App extends Component {
|
|||||||
// stop status timer and disable chat after some time.
|
// stop status timer and disable chat after some time.
|
||||||
handleOfflineMode() {
|
handleOfflineMode() {
|
||||||
clearInterval(this.streamDurationTimer);
|
clearInterval(this.streamDurationTimer);
|
||||||
const remainingChatTime = TIMER_DISABLE_CHAT_AFTER_OFFLINE - (Date.now() - new Date(this.lastDisconnectTime));
|
const remainingChatTime =
|
||||||
const countdown = (remainingChatTime < 0) ? 0 : remainingChatTime;
|
TIMER_DISABLE_CHAT_AFTER_OFFLINE -
|
||||||
|
(Date.now() - new Date(this.lastDisconnectTime));
|
||||||
|
const countdown = remainingChatTime < 0 ? 0 : remainingChatTime;
|
||||||
this.disableChatTimer = setTimeout(this.disableChatInput, countdown);
|
this.disableChatTimer = setTimeout(this.disableChatInput, countdown);
|
||||||
this.setState({
|
this.setState({
|
||||||
streamOnline: false,
|
streamOnline: false,
|
||||||
@@ -270,8 +275,10 @@ export default class App extends Component {
|
|||||||
clearTimeout(this.disableChatTimer);
|
clearTimeout(this.disableChatTimer);
|
||||||
this.disableChatTimer = null;
|
this.disableChatTimer = null;
|
||||||
|
|
||||||
this.streamDurationTimer =
|
this.streamDurationTimer = setInterval(
|
||||||
setInterval(this.setCurrentStreamDuration, TIMER_STREAM_DURATION_COUNTER);
|
this.setCurrentStreamDuration,
|
||||||
|
TIMER_STREAM_DURATION_COUNTER
|
||||||
|
);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
playerActive: true,
|
playerActive: true,
|
||||||
@@ -281,6 +288,17 @@ export default class App extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCurrentStreamDuration() {
|
||||||
|
let streamDurationString = '';
|
||||||
|
if (this.state.lastConnectTime) {
|
||||||
|
const diff = (Date.now() - Date.parse(this.state.lastConnectTime)) / 1000;
|
||||||
|
streamDurationString = secondsToHMMSS(diff);
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
streamStatusMessage: `${MESSAGE_ONLINE} ${streamDurationString}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
handleUsernameChange(newName, newAvatar) {
|
handleUsernameChange(newName, newAvatar) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -348,25 +366,36 @@ export default class App extends Component {
|
|||||||
tags = [],
|
tags = [],
|
||||||
title,
|
title,
|
||||||
} = configData;
|
} = configData;
|
||||||
const { small: smallLogo = TEMP_IMAGE, large: largeLogo = TEMP_IMAGE } = logo;
|
const {
|
||||||
|
small: smallLogo = TEMP_IMAGE,
|
||||||
|
large: largeLogo = TEMP_IMAGE,
|
||||||
|
} = logo;
|
||||||
|
|
||||||
const bgLogo = { backgroundImage: `url(${smallLogo})` };
|
const bgLogo = { backgroundImage: `url(${smallLogo})` };
|
||||||
const bgLogoLarge = { backgroundImage: `url(${largeLogo})` };
|
const bgLogoLarge = { backgroundImage: `url(${largeLogo})` };
|
||||||
|
|
||||||
const tagList = !tags.length ?
|
const tagList = !tags.length
|
||||||
null :
|
? null
|
||||||
tags.map((tag, index) => html`
|
: tags.map(
|
||||||
<li key="tag${index}" class="tag rounded-sm text-gray-100 bg-gray-700 text-xs uppercase mr-3 p-2 whitespace-no-wrap">${tag}</li>
|
(tag, index) => html`
|
||||||
`);
|
<li
|
||||||
|
key="tag${index}"
|
||||||
|
class="tag rounded-sm text-gray-100 bg-gray-700 text-xs uppercase mr-3 p-2 whitespace-no-wrap"
|
||||||
|
>
|
||||||
|
${tag}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
const socialIconsList =
|
const socialIconsList = !socialHandles.length
|
||||||
!socialHandles.length ?
|
? null
|
||||||
null :
|
: socialHandles.map(
|
||||||
socialHandles.map((item, index) => html`
|
(item, index) => html`
|
||||||
<li key="social${index}">
|
<li key="social${index}">
|
||||||
<${SocialIcon} platform=${item.platform} url=${item.url} />
|
<${SocialIcon} platform=${item.platform} url=${item.url} />
|
||||||
</li>
|
</li>
|
||||||
`);
|
`
|
||||||
|
);
|
||||||
|
|
||||||
const mainClass = playerActive ? 'online' : '';
|
const mainClass = playerActive ? 'online' : '';
|
||||||
const streamInfoClass = streamOnline ? 'online' : ''; // need?
|
const streamInfoClass = streamOnline ? 'online' : ''; // need?
|
||||||
@@ -374,35 +403,53 @@ export default class App extends Component {
|
|||||||
const shortHeight = windowHeight <= HEIGHT_SHORT_WIDE;
|
const shortHeight = windowHeight <= HEIGHT_SHORT_WIDE;
|
||||||
const singleColMode = windowWidth <= WIDTH_SINGLE_COL && !shortHeight;
|
const singleColMode = windowWidth <= WIDTH_SINGLE_COL && !shortHeight;
|
||||||
const extraAppClasses = classNames({
|
const extraAppClasses = classNames({
|
||||||
'chat': displayChat,
|
chat: displayChat,
|
||||||
'no-chat': !displayChat,
|
'no-chat': !displayChat,
|
||||||
'single-col': singleColMode,
|
'single-col': singleColMode,
|
||||||
'bg-gray-800': singleColMode && displayChat,
|
'bg-gray-800': singleColMode && displayChat,
|
||||||
'short-wide': shortHeight,
|
'short-wide': shortHeight,
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return html`
|
||||||
html`
|
<div
|
||||||
<div id="app-container" class="flex w-full flex-col justify-start relative ${extraAppClasses}">
|
id="app-container"
|
||||||
|
class="flex w-full flex-col justify-start relative ${extraAppClasses}"
|
||||||
|
>
|
||||||
<div id="top-content" class="z-50">
|
<div id="top-content" class="z-50">
|
||||||
<header class="flex border-b border-gray-900 border-solid shadow-md fixed z-10 w-full top-0 left-0 flex flex-row justify-between flex-no-wrap">
|
<header
|
||||||
<h1 class="flex flex-row items-center justify-start p-2 uppercase text-gray-400 text-xl font-thin tracking-wider overflow-hidden whitespace-no-wrap">
|
class="flex border-b border-gray-900 border-solid shadow-md fixed z-10 w-full top-0 left-0 flex flex-row justify-between flex-no-wrap"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="flex flex-row items-center justify-start p-2 uppercase text-gray-400 text-xl font-thin tracking-wider overflow-hidden whitespace-no-wrap"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
id="logo-container"
|
id="logo-container"
|
||||||
class="inline-block rounded-full bg-white w-8 min-w-8 min-h-8 h-8 p-1 mr-2 bg-no-repeat bg-center"
|
class="inline-block rounded-full bg-white w-8 min-w-8 min-h-8 h-8 p-1 mr-2 bg-no-repeat bg-center"
|
||||||
style=${bgLogo}
|
style=${bgLogo}
|
||||||
>
|
>
|
||||||
<img class="logo visually-hidden" src=${smallLogo} alt=""/>
|
<img class="logo visually-hidden" src=${smallLogo} alt="" />
|
||||||
</span>
|
</span>
|
||||||
<span class="instance-title overflow-hidden truncate">${title}</span>
|
<span class="instance-title overflow-hidden truncate"
|
||||||
|
>${title}</span
|
||||||
|
>
|
||||||
</h1>
|
</h1>
|
||||||
<div id="user-options-container" class="flex flex-row justify-end items-center flex-no-wrap">
|
<div
|
||||||
|
id="user-options-container"
|
||||||
|
class="flex flex-row justify-end items-center flex-no-wrap"
|
||||||
|
>
|
||||||
<${UsernameForm}
|
<${UsernameForm}
|
||||||
username=${username}
|
username=${username}
|
||||||
userAvatarImage=${userAvatarImage}
|
userAvatarImage=${userAvatarImage}
|
||||||
handleUsernameChange=${this.handleUsernameChange}
|
handleUsernameChange=${this.handleUsernameChange}
|
||||||
/>
|
/>
|
||||||
<button type="button" id="chat-toggle" onClick=${this.handleChatPanelToggle} class="flex cursor-pointer text-center justify-center items-center min-w-12 h-full bg-gray-800 hover:bg-gray-700">💬</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
id="chat-toggle"
|
||||||
|
onClick=${this.handleChatPanelToggle}
|
||||||
|
class="flex cursor-pointer text-center justify-center items-center min-w-12 h-full bg-gray-800 hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
💬
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
@@ -422,10 +469,17 @@ export default class App extends Component {
|
|||||||
></video>
|
></video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section id="stream-info" aria-label="Stream status" class="flex text-center flex-row justify-between font-mono py-2 px-8 bg-gray-900 text-indigo-200 shadow-md border-b border-gray-100 border-solid ${streamInfoClass}">
|
<section
|
||||||
|
id="stream-info"
|
||||||
|
aria-label="Stream status"
|
||||||
|
class="flex text-center flex-row justify-between font-mono py-2 px-8 bg-gray-900 text-indigo-200 shadow-md border-b border-gray-100 border-solid ${streamInfoClass}"
|
||||||
|
>
|
||||||
<span>${streamStatusMessage}</span>
|
<span>${streamStatusMessage}</span>
|
||||||
<span>${viewerCount} ${pluralize('viewer', viewerCount)}.</span>
|
<span>${viewerCount} ${pluralize('viewer', viewerCount)}.</span>
|
||||||
<span>${sessionMaxViewerCount} Max ${pluralize('viewer', sessionMaxViewerCount)}.</span>
|
<span
|
||||||
|
>${sessionMaxViewerCount} Max
|
||||||
|
${pluralize('viewer', sessionMaxViewerCount)}.</span
|
||||||
|
>
|
||||||
<span>${overallMaxViewerCount} overall.</span>
|
<span>${overallMaxViewerCount} overall.</span>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
@@ -436,20 +490,31 @@ export default class App extends Component {
|
|||||||
class="user-image rounded-full bg-white p-4 mr-8 bg-no-repeat bg-center"
|
class="user-image rounded-full bg-white p-4 mr-8 bg-no-repeat bg-center"
|
||||||
style=${bgLogoLarge}
|
style=${bgLogoLarge}
|
||||||
>
|
>
|
||||||
<img
|
<img class="logo visually-hidden" alt="Logo" src=${largeLogo} />
|
||||||
class="logo visually-hidden"
|
|
||||||
alt="Logo"
|
|
||||||
src=${largeLogo}/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="user-content-header border-b border-gray-500 border-solid">
|
<div
|
||||||
|
class="user-content-header border-b border-gray-500 border-solid"
|
||||||
|
>
|
||||||
<h2 class="font-semibold text-5xl">
|
<h2 class="font-semibold text-5xl">
|
||||||
About <span class="streamer-name text-indigo-600">${streamerName}</span>
|
About
|
||||||
|
<span class="streamer-name text-indigo-600"
|
||||||
|
>${streamerName}</span
|
||||||
|
>
|
||||||
</h2>
|
</h2>
|
||||||
<ul id="social-list" class="social-list flex flex-row items-center justify-start flex-wrap">
|
<ul
|
||||||
<span class="follow-label text-xs font-bold mr-2 uppercase">Follow me: </span>
|
id="social-list"
|
||||||
|
class="social-list flex flex-row items-center justify-start flex-wrap"
|
||||||
|
>
|
||||||
|
<span class="follow-label text-xs font-bold mr-2 uppercase"
|
||||||
|
>Follow me:
|
||||||
|
</span>
|
||||||
${socialIconsList}
|
${socialIconsList}
|
||||||
</ul>
|
</ul>
|
||||||
<div id="stream-summary" class="stream-summary my-4" dangerouslySetInnerHTML=${{ __html: summary }}></div>
|
<div
|
||||||
|
id="stream-summary"
|
||||||
|
class="stream-summary my-4"
|
||||||
|
dangerouslySetInnerHTML=${{ __html: summary }}
|
||||||
|
></div>
|
||||||
<ul id="tag-list" class="tag-list flex flex-row my-4">
|
<ul id="tag-list" class="tag-list flex flex-row my-4">
|
||||||
${tagList}
|
${tagList}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -476,8 +541,7 @@ export default class App extends Component {
|
|||||||
chatEnabled=${chatEnabled}
|
chatEnabled=${chatEnabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
`
|
`;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user