19 changed files with 191 additions and 103 deletions
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
.button { |
||||
margin: 3px; |
||||
.icon { |
||||
width: 20px; |
||||
margin-right: 3px; |
||||
} |
||||
} |
||||
@ -1,17 +1,18 @@
@@ -1,17 +1,18 @@
|
||||
import { Button } from 'antd'; |
||||
import { ExternalAction } from '../interfaces/external-action.interface'; |
||||
import s from './ActionButton.module.scss'; |
||||
|
||||
interface Props { |
||||
action: ExternalAction; |
||||
} |
||||
|
||||
export default function ExternalActionButton(props: Props) { |
||||
export default function ActionButton(props: Props) { |
||||
const { action } = props; |
||||
const { url, title, description, icon, color, openExternally } = action; |
||||
|
||||
return ( |
||||
<Button type="primary" style={{ backgroundColor: color }}> |
||||
<img src={icon} width="30px" alt={description} /> |
||||
<Button type="primary" className={`${s.button}`} style={{ backgroundColor: color }}> |
||||
<img src={icon} className={`${s.icon}`} alt={description} /> |
||||
{title} |
||||
</Button> |
||||
); |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
import React from 'react'; |
||||
import s from './ActionButtons.module.scss'; |
||||
|
||||
interface Props { |
||||
children: React.ReactNode[]; |
||||
} |
||||
|
||||
export default function ActionButtonRow(props: Props) { |
||||
const { children } = props; |
||||
|
||||
return <div className={`${s.row}`}>{children}</div>; |
||||
} |
||||
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
.row { |
||||
margin: 5px; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: flex-end; |
||||
button { |
||||
margin-left: 5px; |
||||
margin-right: 5px; |
||||
} |
||||
} |
||||
@ -1,19 +0,0 @@
@@ -1,19 +0,0 @@
|
||||
import { ExternalAction } from '../interfaces/external-action.interface'; |
||||
import ExternalActionButton from './ExternalActionButton'; |
||||
import s from './ExternalActionButtons.module.scss'; |
||||
|
||||
interface Props { |
||||
actions: ExternalAction[]; |
||||
} |
||||
|
||||
export default function ExternalActionButtonRow(props: Props) { |
||||
const { actions } = props; |
||||
|
||||
return ( |
||||
<div className={`${s.row}`}> |
||||
{actions.map(action => ( |
||||
<ExternalActionButton key={action.id} action={action} /> |
||||
))} |
||||
</div> |
||||
); |
||||
} |
||||
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
.statusbar { |
||||
display: flex; |
||||
align-items: center; |
||||
padding-left: 10px; |
||||
padding-right: 10px; |
||||
justify-content: space-between; |
||||
height: 30px; |
||||
width: 100%; |
||||
background-color: black; |
||||
} |
||||
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
import formatDistanceToNow from 'date-fns/formatDistanceToNow'; |
||||
import formatDistanceToNowStrict from 'date-fns/formatDistanceToNowStrict'; |
||||
import { useEffect, useState } from 'react'; |
||||
|
||||
import s from './Statusbar.module.scss'; |
||||
|
||||
interface Props { |
||||
online: Boolean; |
||||
lastConnectTime?: Date; |
||||
lastDisconnectTime?: Date; |
||||
viewerCount: number; |
||||
} |
||||
export default function Statusbar(props: Props) { |
||||
const [now, setNow] = useState(new Date()); |
||||
|
||||
useEffect(() => { |
||||
const interval = setInterval(() => setNow(new Date()), 1000); |
||||
return () => { |
||||
clearInterval(interval); |
||||
}; |
||||
}, []); |
||||
|
||||
const { online, lastConnectTime, lastDisconnectTime, viewerCount } = props; |
||||
|
||||
let onlineMessage = ''; |
||||
let rightSideMessage = ''; |
||||
if (online && lastConnectTime) { |
||||
const diff = formatDistanceToNowStrict(new Date(lastConnectTime)); |
||||
onlineMessage = online ? `Streaming for ${diff}` : 'Offline'; |
||||
rightSideMessage = `${viewerCount} viewers`; |
||||
} else { |
||||
onlineMessage = 'Offline'; |
||||
if (lastDisconnectTime) { |
||||
rightSideMessage = `Last live ${formatDistanceToNow(lastDisconnectTime)} ago.`; |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<div className={s.statusbar}> |
||||
{/* <div>{streamStartedAt}</div> */} |
||||
<div>{onlineMessage}</div> |
||||
<div>{rightSideMessage}</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
Statusbar.defaultProps = { |
||||
lastConnectTime: null, |
||||
lastDisconectTime: null, |
||||
}; |
||||
@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
|
||||
.player { |
||||
height: 90vh; |
||||
height: 80vh; |
||||
width: 100%; |
||||
background-color: green; |
||||
} |
||||
@ -1,9 +0,0 @@
@@ -1,9 +0,0 @@
|
||||
interface Props { |
||||
online: boolean; |
||||
viewers: number; |
||||
timer: string; |
||||
} |
||||
|
||||
export default function StatusBar(props: Props) { |
||||
return <div>Stream status bar goes here</div>; |
||||
} |
||||
@ -1,17 +1,15 @@
@@ -1,17 +1,15 @@
|
||||
import React from 'react'; |
||||
import { ComponentStory, ComponentMeta } from '@storybook/react'; |
||||
import ExternalActionButton from '../components/action-buttons/ExternalActionButton'; |
||||
import ActionButton from '../components/action-buttons/ActionButton'; |
||||
|
||||
export default { |
||||
title: 'owncast/External action button', |
||||
component: ExternalActionButton, |
||||
component: ActionButton, |
||||
parameters: {}, |
||||
} as ComponentMeta<typeof ExternalActionButton>; |
||||
} as ComponentMeta<typeof ActionButton>; |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ExternalActionButton> = args => ( |
||||
<ExternalActionButton {...args} /> |
||||
); |
||||
const Template: ComponentStory<typeof ActionButton> = args => <ActionButton {...args} />; |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Example1 = Template.bind({}); |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
import React from 'react'; |
||||
import { ComponentStory, ComponentMeta } from '@storybook/react'; |
||||
import ActionButtonRow from '../components/action-buttons/ActionButtonRow'; |
||||
import ActionButton from '../components/action-buttons/ActionButton'; |
||||
|
||||
export default { |
||||
title: 'owncast/External action button row', |
||||
component: ActionButtonRow, |
||||
parameters: {}, |
||||
} as ComponentMeta<typeof ActionButtonRow>; |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ActionButtonRow> = args => ( |
||||
<ActionButtonRow>{args.buttons}</ActionButtonRow> |
||||
); |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const actions = [ |
||||
{ |
||||
url: 'https://owncast.online/docs', |
||||
title: 'Documentation', |
||||
description: 'Owncast Documentation', |
||||
icon: 'https://owncast.online/images/logo.svg', |
||||
color: '#5232c8', |
||||
openExternally: false, |
||||
}, |
||||
{ |
||||
url: 'https://opencollective.com/embed/owncast/donate', |
||||
title: 'Support Owncast', |
||||
description: 'Contribute to Owncast', |
||||
icon: 'https://opencollective.com/static/images/opencollective-icon.svg', |
||||
color: '#2b4863', |
||||
openExternally: false, |
||||
}, |
||||
]; |
||||
|
||||
const buttons = actions.map(action => <ActionButton action={action} />); |
||||
export const Example1 = Template.bind({}); |
||||
Example1.args = { |
||||
buttons, |
||||
}; |
||||
@ -1,37 +0,0 @@
@@ -1,37 +0,0 @@
|
||||
import React from 'react'; |
||||
import { ComponentStory, ComponentMeta } from '@storybook/react'; |
||||
import ExternalActionButtonRow from '../components/action-buttons/ExternalActionButtonRow'; |
||||
|
||||
export default { |
||||
title: 'owncast/External action button row', |
||||
component: ExternalActionButtonRow, |
||||
parameters: {}, |
||||
} as ComponentMeta<typeof ExternalActionButtonRow>; |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ExternalActionButtonRow> = args => ( |
||||
<ExternalActionButtonRow {...args} /> |
||||
); |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Example1 = Template.bind({}); |
||||
Example1.args = { |
||||
actions: [ |
||||
{ |
||||
url: 'https://owncast.online/docs', |
||||
title: 'Documentation', |
||||
description: 'Owncast Documentation', |
||||
icon: 'https://owncast.online/images/logo.svg', |
||||
color: '#5232c8', |
||||
openExternally: false, |
||||
}, |
||||
{ |
||||
url: 'https://opencollective.com/embed/owncast/donate', |
||||
title: 'Support Owncast', |
||||
description: 'Contribute to Owncast', |
||||
icon: 'https://opencollective.com/static/images/opencollective-icon.svg', |
||||
color: '#2b4863', |
||||
openExternally: false, |
||||
}, |
||||
], |
||||
}; |
||||
@ -1,25 +1,25 @@
@@ -1,25 +1,25 @@
|
||||
import React from 'react'; |
||||
import { ComponentStory, ComponentMeta } from '@storybook/react'; |
||||
import StatusBar from '../components/video/StatusBar'; |
||||
import { subHours } from 'date-fns'; |
||||
import Statusbar from '../components/ui/Statusbar/Statusbar'; |
||||
|
||||
export default { |
||||
title: 'owncast/Status bar', |
||||
component: StatusBar, |
||||
component: Statusbar, |
||||
parameters: {}, |
||||
} as ComponentMeta<typeof StatusBar>; |
||||
} as ComponentMeta<typeof Statusbar>; |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof StatusBar> = args => <StatusBar {...args} />; |
||||
const Template: ComponentStory<typeof Statusbar> = args => <Statusbar {...args} />; |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Online = Template.bind({}); |
||||
Online.args = { |
||||
online: true, |
||||
viewers: 42, |
||||
timer: '10:42', |
||||
viewerCount: 42, |
||||
lastConnectTime: subHours(new Date(), 3), |
||||
}; |
||||
|
||||
export const Offline = Template.bind({}); |
||||
Offline.args = { |
||||
online: false, |
||||
lastDisconnectTime: subHours(new Date(), 3), |
||||
}; |
||||
|
||||
Loading…
Reference in new issue