added cal event generation, lots of styling
This commit is contained in:
parent
b932d96d80
commit
e6e05d90ef
68
index.js
68
index.js
@ -1,27 +1,56 @@
|
||||
import { h, render } from 'preact'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { Fragment, h, render } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { ThemeProvider } from 'styled-components'
|
||||
import { BrowserRouter, Route, Switch } from 'react-router-dom'
|
||||
import { isWithinInterval, subHours, addHours } from 'date-fns'
|
||||
import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'
|
||||
|
||||
import Main from './app'
|
||||
import SeriesPage from './src/pages/SeriesPage'
|
||||
import { useEventApi } from './src/hooks/data'
|
||||
import { useTheme } from './src/store'
|
||||
import { useEventApi, usePeertubeApi } from './src/hooks/data'
|
||||
import { useStreamStore, useTheme } from './src/store'
|
||||
import { useTimeout } from './src/hooks/timerHooks'
|
||||
import LoaderLayout from './src/pages/LoaderLayout'
|
||||
import FourOhFour from './src/pages/404'
|
||||
import Series from './src/pages/Series'
|
||||
import Program from './src/pages/Program'
|
||||
import StreamPreview from './src/components/StreamPreview'
|
||||
|
||||
const App = () => {
|
||||
const { theme } = useTheme((store) => store)
|
||||
const { data, loading: eventsLoading } = useEventApi()
|
||||
const [minLoadTimePassed, setMinTimeUp] = useState(false)
|
||||
const { setCurrentStream, currentStream, streamIsLive } = useStreamStore(store => store)
|
||||
usePeertubeApi(data.episodes)
|
||||
|
||||
useTimeout(() => {
|
||||
setMinTimeUp(true)
|
||||
}, 1500)
|
||||
|
||||
useEffect(() => {
|
||||
if (Array.isArray(data.episodes)) {
|
||||
data.episodes.forEach(stream => {
|
||||
const utcStartDate = zonedTimeToUtc(
|
||||
new Date(stream.beginsOn),
|
||||
'Europe/Berlin'
|
||||
)
|
||||
const utcEndDate = zonedTimeToUtc(new Date(stream.endsOn), 'Europe/Berlin')
|
||||
const { timeZone } = Intl.DateTimeFormat().resolvedOptions()
|
||||
|
||||
const zonedStartDate = utcToZonedTime(utcStartDate, timeZone)
|
||||
const zonedEndDate = utcToZonedTime(utcEndDate, timeZone)
|
||||
if (
|
||||
isWithinInterval(new Date(), {
|
||||
start: subHours(zonedStartDate, 1),
|
||||
end: addHours(zonedEndDate, 1),
|
||||
})
|
||||
) {
|
||||
setCurrentStream(stream)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [eventsLoading])
|
||||
|
||||
// console.log({ episodes: data.episodes, series: data.series })
|
||||
|
||||
const seriesData = data.series ? Object.values(data.series) : []
|
||||
@ -32,20 +61,23 @@ const App = () => {
|
||||
{!seriesData.length || eventsLoading || !minLoadTimePassed ? (
|
||||
<LoaderLayout />
|
||||
) : (
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Main} />
|
||||
<Route exact path="/series" component={Series} />
|
||||
<Route exact path="/program" component={Program} />
|
||||
{seriesData.length ? seriesData.map(series => (
|
||||
<Route exact path={`/series/${series.slug}`}>
|
||||
<SeriesPage data={series} />
|
||||
</Route>)) : null}
|
||||
<Route path="*">
|
||||
<FourOhFour />
|
||||
</Route>
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
<Fragment>
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Main} />
|
||||
<Route exact path="/series" component={Series} />
|
||||
<Route exact path="/program" component={Program} />
|
||||
{seriesData.length ? seriesData.map(series => (
|
||||
<Route exact path={`/series/${series.slug}`}>
|
||||
<SeriesPage data={series} />
|
||||
</Route>)) : null}
|
||||
<Route path="*">
|
||||
<FourOhFour />
|
||||
</Route>
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
<StreamPreview stream={currentStream} isLive={streamIsLive} />
|
||||
</Fragment>
|
||||
)}
|
||||
</ThemeProvider>)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
"axios": "^0.21.1",
|
||||
"date-fns": "^2.19.0",
|
||||
"date-fns-tz": "^1.1.4",
|
||||
"datebook": "^7.0.7",
|
||||
"dotenv": "^10.0.0",
|
||||
"ical": "^0.8.0",
|
||||
"ical.js": "^1.4.0",
|
||||
|
@ -31,6 +31,7 @@ export const screenSizes = {
|
||||
sm: 800,
|
||||
md: 1000,
|
||||
lg: 1500,
|
||||
xl: 1700,
|
||||
}
|
||||
|
||||
export const defaultTheme = {
|
||||
|
@ -1,26 +1,37 @@
|
||||
import { h } from 'preact'
|
||||
import { ICalendar } from 'datebook'
|
||||
import { format } from 'date-fns'
|
||||
import striptags from 'striptags'
|
||||
import Link from '../Link'
|
||||
import { H2, H3, Label } from '../Text'
|
||||
import strings from '../../data/strings'
|
||||
import { andList } from '../../helpers/string'
|
||||
import { colours } from '../../assets/theme'
|
||||
import { Img, Left, Right, Center, Title, Row, Column, StyledButton as Button } from './styles'
|
||||
import { colours, screenSizes } from '../../assets/theme'
|
||||
import { Img, Left, Right, Center, Title, ButtonRow, StyledButton as Button } from './styles'
|
||||
import { useEventApi } from '../../hooks/data'
|
||||
import { useWindowSize } from '../../hooks/dom'
|
||||
import Flex from '../Flex'
|
||||
|
||||
const EpisodeCard = ({ image, title, seriesId, beginsOn, id, ...rest }) => {
|
||||
|
||||
|
||||
const EpisodeCard = ({ image, title, seriesId, beginsOn, endsOn, id, url, description, ...rest }) => {
|
||||
const { data: { series: allSeries } } = useEventApi()
|
||||
|
||||
const series = seriesId ? allSeries.filter(({ id }) => id === seriesId)[0] : {}
|
||||
const hosts = series.hosts ? series.hosts.map(host => host.actor.name) : null
|
||||
const startTime = format(new Date(beginsOn), 'ha')
|
||||
const startTime = format(new Date(beginsOn), 'h:mma')
|
||||
|
||||
const { width: screenWidth } = useWindowSize()
|
||||
const isMobile = screenWidth < screenSizes.md
|
||||
|
||||
|
||||
return (
|
||||
<Row align="stretch" {...rest}>
|
||||
<Flex align="stretch" direction={isMobile ? 'column' : 'row'} {...rest}>
|
||||
<Left>
|
||||
<Link to={`/series/${series.slug}#${id}`}><Img src={image} /></Link>
|
||||
<Button>Play now</Button>
|
||||
<Link to={`/series/${series.slug}#${id}`}>
|
||||
<Img src={image} />
|
||||
</Link>
|
||||
<ButtonsRows title={title} description={description} beginsOn={beginsOn} endsOn={endsOn} url={url} />
|
||||
</Left>
|
||||
<Center justify="space-betweeen">
|
||||
<Title size={24} colour={colours.rose} weight="500">{title}</Title>
|
||||
@ -34,9 +45,28 @@ const EpisodeCard = ({ image, title, seriesId, beginsOn, id, ...rest }) => {
|
||||
<Right>
|
||||
<Label size={24} colour={colours.rose} weight="600">{startTime}</Label>
|
||||
</Right>
|
||||
</Row>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export const ButtonsRows = ({ title, description, beginsOn, endsOn, url }) => {
|
||||
const icalendar = new ICalendar({
|
||||
title,
|
||||
location: 'https://stream.undersco.re/',
|
||||
description: description ? striptags(description) : '',
|
||||
start: new Date(beginsOn),
|
||||
end: new Date(endsOn),
|
||||
})
|
||||
|
||||
const dlIcal = () => icalendar.download()
|
||||
return (
|
||||
<ButtonRow justify="space-between">
|
||||
<Button onClick={dlIcal}>{strings.en.subEvent}</Button>
|
||||
<a href={url} target="_blank" rel="noopener noreferrer">
|
||||
<Button>{strings.en.eventDetails}</Button>
|
||||
</a>
|
||||
</ButtonRow>)
|
||||
}
|
||||
|
||||
|
||||
export default EpisodeCard
|
||||
|
@ -1,43 +1,92 @@
|
||||
import styled from 'styled-components'
|
||||
import { colours } from '../../assets/theme'
|
||||
import { colours, screenSizes } from '../../assets/theme'
|
||||
import { Label, H2 } from '../Text'
|
||||
import { Row as FlexRow, Column as FlexColumn } from '../Flex'
|
||||
import Flexbox, { Row as FlexRow, Column as FlexColumn } from '../Flex'
|
||||
import Button from '../Button'
|
||||
|
||||
export const Row = styled(FlexRow)`
|
||||
export const ButtonRow = styled(Flexbox)`
|
||||
width: 100%;
|
||||
align-items: stretch;
|
||||
|
||||
button, a{
|
||||
font-size: 16px;
|
||||
width: 49%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: ${screenSizes.md}px) and (max-width: ${screenSizes.lg}px) {
|
||||
flex-direction: column;
|
||||
|
||||
button, a {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
export const Column = styled(FlexColumn)`
|
||||
`
|
||||
|
||||
export const Left = styled(FlexColumn)`
|
||||
margin-right: 1em;
|
||||
width: 20vw;
|
||||
|
||||
@media screen and (max-width: ${screenSizes.md}px) {
|
||||
width: 80vw;
|
||||
margin-right: 0em;
|
||||
}
|
||||
`
|
||||
|
||||
export const Center = styled(FlexColumn)`
|
||||
max-width: 60%;
|
||||
`
|
||||
@media screen and (max-width: ${screenSizes.md}px) {
|
||||
order: 2;
|
||||
}
|
||||
`
|
||||
|
||||
export const Title = styled(H2)`
|
||||
max-width: 60%;
|
||||
|
||||
@media screen and (max-width: ${screenSizes.md}px) {
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${screenSizes.sm}px) {
|
||||
max-width: 70%;
|
||||
}
|
||||
margin-bottom: 1em;
|
||||
`
|
||||
`
|
||||
|
||||
export const Right = styled.div`
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
|
||||
@media screen and (max-width: ${screenSizes.md}px) {
|
||||
position: relative;
|
||||
top: 1em;
|
||||
order: 1;
|
||||
}
|
||||
`
|
||||
export const StyledButton = styled(Button)`
|
||||
/* width: max-content; */
|
||||
margin-top: 0.5em;
|
||||
padding: 0.3em 2em;
|
||||
|
||||
@media screen and (max-width: ${screenSizes.md}px) {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
`
|
||||
|
||||
export const Img = styled.div`
|
||||
background: url(${({ src }) => src});
|
||||
width: 25vw;
|
||||
/* height: 215px; */
|
||||
width: 20vw;
|
||||
padding-bottom: calc((9 / 16) * 100%);
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
background-position: center;
|
||||
|
||||
@media screen and (max-width: ${screenSizes.md}px) {
|
||||
width: 80vw;
|
||||
}
|
||||
`
|
@ -1,6 +1,16 @@
|
||||
import { bool, number, oneOf } from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const Flexbox = styled.div`
|
||||
display: flex;
|
||||
flex-direction: ${props => (props.direction || 'row')};
|
||||
justify-content: ${props => props.justify || 'flex-start'};
|
||||
align-items: ${props => props.align || 'flex-start'};
|
||||
${props => props.flex ? `
|
||||
flex: ${props.flex};
|
||||
` : ''}
|
||||
`
|
||||
|
||||
export const Row = styled.div`
|
||||
display: flex;
|
||||
flex-direction: ${props => (props.reverse ? 'row-reverse' : 'row')};
|
||||
@ -28,4 +38,6 @@ const propTypes = {
|
||||
}
|
||||
|
||||
Row.propTypes = propTypes
|
||||
Column.propTypes = propTypes
|
||||
Column.propTypes = propTypes
|
||||
|
||||
export default Flexbox
|
8
src/components/StreamPreview/helpers.js
Normal file
8
src/components/StreamPreview/helpers.js
Normal file
@ -0,0 +1,8 @@
|
||||
import strings from '../../data/strings'
|
||||
|
||||
export const getLabel = (stream, isLive, isMinimized) => {
|
||||
const currentLanguage = 'en'
|
||||
const prefix = isLive ? strings[currentLanguage].nowPlaying : strings[currentLanguage].startingSoon
|
||||
if (isMinimized) return `${prefix}: ${stream.title}`
|
||||
return prefix
|
||||
}
|
65
src/components/StreamPreview/index.js
Normal file
65
src/components/StreamPreview/index.js
Normal file
@ -0,0 +1,65 @@
|
||||
import { Fragment, h } from 'preact'
|
||||
import { useEffect, useRef } from 'preact/hooks'
|
||||
import { PeerTubePlayer } from '@peertube/embed-api'
|
||||
import { string } from 'prop-types'
|
||||
import Link from '../Link'
|
||||
import { Label } from '../Text'
|
||||
import strings from '../../data/strings'
|
||||
import { Box, Img, Iframe } from './styles'
|
||||
import { colours, textSizes } from '../../assets/theme'
|
||||
import { Row } from '../Flex'
|
||||
import CrossSvg from '../Svg/Cross'
|
||||
import { useUiStore } from '../../store'
|
||||
import { getLabel } from './helpers'
|
||||
import Chevron from '../Svg/Chevron'
|
||||
// import { useEventApi } from '../../hooks/data'
|
||||
|
||||
const StreamPreview = ({ stream, isLive, ...rest }) => {
|
||||
const currentLanguage = 'en'
|
||||
const videoiFrame = useRef(null)
|
||||
const ptVideo = useRef(null)
|
||||
const { isMinimized, toggleMinimized } = useUiStore(store => ({ isMinimized: store.streamPreviewMinimized, toggleMinimized: store.toggleStreamPreviewMinimized }))
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const setupAndPlayVideo = async () => {
|
||||
const player = new PeerTubePlayer(videoiFrame.current)
|
||||
await player.ready
|
||||
|
||||
ptVideo.current = player
|
||||
player.setVolume(0)
|
||||
player.play()
|
||||
}
|
||||
if (isLive) {
|
||||
setupAndPlayVideo()
|
||||
}
|
||||
}, [isLive, isMinimized])
|
||||
|
||||
return stream ? (
|
||||
<Box isMinimized={isMinimized}>
|
||||
<Row justify="space-between">
|
||||
<Label colour={colours.midnightDarker} size={textSizes.lg}>{getLabel(stream, isLive, isMinimized)}</Label>
|
||||
{isMinimized ? <Chevron colour={colours.midnightDarker} size={14} onClick={toggleMinimized} /> : <CrossSvg colour={colours.midnightDarker} size={16} onClick={toggleMinimized} />}
|
||||
</Row>
|
||||
{!isMinimized ?
|
||||
<Fragment>
|
||||
{isLive ?
|
||||
<Iframe
|
||||
width="560"
|
||||
height="315"
|
||||
sandbox="allow-same-origin allow-scripts allow-popups"
|
||||
autoplay="autoplay"
|
||||
muted="muted"
|
||||
src={`https://tv.undersco.re${stream.embedPath}?api=1&controls=false`}
|
||||
frameBorder="0"
|
||||
allowFullScreen
|
||||
ref={videoiFrame}
|
||||
/>
|
||||
: <Img src={stream.image} />}
|
||||
</Fragment> : null}
|
||||
</Box>
|
||||
) : null
|
||||
}
|
||||
|
||||
|
||||
export default StreamPreview
|
57
src/components/StreamPreview/styles.js
Normal file
57
src/components/StreamPreview/styles.js
Normal file
@ -0,0 +1,57 @@
|
||||
import styled from 'styled-components'
|
||||
import { colours, screenSizes } from '../../assets/theme'
|
||||
|
||||
export const Box = styled.div`
|
||||
position: fixed;
|
||||
bottom: ${props => props.isMinimized ? 0 : '2em'};
|
||||
right: ${props => props.isMinimized ? 0 : '2em'};
|
||||
background-color: ${colours.white};
|
||||
padding: ${props => props.isMinimized ? '0.2em 0.2em' : '0.5em 0.5em'};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: ${props => props.isMinimized ? 0 : '0.5em'};
|
||||
margin-right: ${props => props.isMinimized ? '0.5em' : 0};
|
||||
font-size: ${props => props.isMinimized ? '15' : '21'}px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${screenSizes.xs}px) {
|
||||
bottom: 0em;
|
||||
right: 0em;
|
||||
}
|
||||
`
|
||||
export const Img = styled.div`
|
||||
background: url(${({ src }) => src});
|
||||
width: 16vw;
|
||||
padding-bottom: calc((9 / 16) * 100%);
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
background-position: center;
|
||||
|
||||
@media screen and (max-width: ${screenSizes.xl}px) {
|
||||
width: 20vw;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${screenSizes.lg}px) {
|
||||
width: 25vw;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${screenSizes.md}px) {
|
||||
width: 33vw;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${screenSizes.sm}px) {
|
||||
width: 50vw;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${screenSizes.xs}px) {
|
||||
width: 60vw;
|
||||
}
|
||||
`
|
||||
|
||||
export const Iframe = styled.iframe`
|
||||
width: 20vw;
|
||||
height: 11.2vw;
|
||||
`
|
@ -1,8 +1,9 @@
|
||||
import { h } from 'preact'
|
||||
import { Svg } from './base'
|
||||
import { svgPropTypes } from './proptypes'
|
||||
|
||||
const Chevron = ({ colour = 'inherit', size, ...rest }) => (
|
||||
<svg
|
||||
<Svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
height={size}
|
||||
@ -15,7 +16,7 @@ const Chevron = ({ colour = 'inherit', size, ...rest }) => (
|
||||
d="M49.68 19.005l.002-.003 46.733 46.733-14.849 14.85-31.889-31.89-30.889 30.89L3.94 64.735 49.675 19l.005.005z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</Svg>
|
||||
)
|
||||
|
||||
Chevron.propTypes = {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { h } from 'preact'
|
||||
import { Svg } from './base'
|
||||
|
||||
import { svgPropTypes } from './proptypes'
|
||||
|
||||
const Cross = ({ colour = 'inherit', size, ...rest }) => (
|
||||
<svg viewBox="0 0 100 100" height={size} {...rest}>
|
||||
<Svg viewBox="0 0 100 100" height={size} {...rest}>
|
||||
<path
|
||||
stroke={colour}
|
||||
strokeLinecap="none"
|
||||
@ -11,7 +12,7 @@ const Cross = ({ colour = 'inherit', size, ...rest }) => (
|
||||
strokeWidth="18"
|
||||
d="M11.354 11.757l77.637 77.636m-77.627 0L89 11.757"
|
||||
/>
|
||||
</svg>
|
||||
</Svg>
|
||||
)
|
||||
|
||||
Cross.propTypes = {
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { h } from 'preact'
|
||||
import { number, string } from 'prop-types'
|
||||
import { Svg } from './base'
|
||||
|
||||
const Play = ({ size = '24', colour = 'inherit', ...rest }) => (
|
||||
<svg viewBox="0 0 24 24" height={size} width={size} {...rest}>
|
||||
<Svg viewBox="0 0 24 24" height={size} width={size} {...rest}>
|
||||
<path
|
||||
strokeWidth="1.5"
|
||||
stroke={colour}
|
||||
fill="transparent"
|
||||
d="M2.12436,1.73205 C2.12436,0.96225 2.95769,0.481125 3.62436,0.866025 L21.6244,11.2583 C22.291,11.6432 22.291,12.6055 21.6244,12.9904 L3.62436,23.3827 C2.95769,23.7676 2.12436,23.2865 2.12436,22.5167 L2.12436,1.73205 Z"
|
||||
/>
|
||||
</svg>
|
||||
</Svg>
|
||||
)
|
||||
|
||||
Play.propTypes = {
|
||||
|
7
src/components/Svg/base.js
Normal file
7
src/components/Svg/base.js
Normal file
@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const Svg = styled.svg`
|
||||
${props => props.onClick ? `
|
||||
cursor: pointer;
|
||||
` : ''}
|
||||
`
|
@ -2,7 +2,8 @@ export default {
|
||||
en: {
|
||||
program: 'Program',
|
||||
pastStream: 'Previous Episodes',
|
||||
nowPlaying: 'Now playing',
|
||||
nowPlaying: 'Currently streaming',
|
||||
startingSoon: 'Starting soon',
|
||||
noStreams: 'No upcoming streams, check back soon.',
|
||||
underscoreTagline: ['LEAVE THE', 'SURVEILLANCE ECONOMY', '— TOGETHER.'],
|
||||
streamDateFuture: 'Going live at: ',
|
||||
@ -21,6 +22,7 @@ export default {
|
||||
nextStream: 'Next stream',
|
||||
episodes: 'episodes',
|
||||
today: 'today',
|
||||
tomorrow: 'tomorrow'
|
||||
tomorrow: 'tomorrow',
|
||||
eventDetails: 'Event details',
|
||||
},
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { h, render } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import axios from 'axios'
|
||||
import ICAL from 'ical.js'
|
||||
import config from '../data/config'
|
||||
import { useSeriesStore } from '../store/index'
|
||||
import { useSeriesStore, useStreamStore } from '../store/index'
|
||||
import 'regenerator-runtime/runtime'
|
||||
import { useInterval } from './timerHooks'
|
||||
import { secondsToMilliseconds } from 'date-fns'
|
||||
|
||||
export const useEventCalendar = () => {
|
||||
const [data, setData] = useState([])
|
||||
@ -112,6 +115,7 @@ export const useEventApi = () => {
|
||||
|
||||
|
||||
setData(responseData)
|
||||
console.log({ data: responseData })
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
@ -121,4 +125,31 @@ export const useEventApi = () => {
|
||||
}, [])
|
||||
|
||||
return { loading, data }
|
||||
}
|
||||
|
||||
export const usePeertubeApi = async () => {
|
||||
const { currentStream, setCurrentStream, setStreamIsLive, streamIsLive } = useStreamStore(store => store)
|
||||
|
||||
if (!currentStream) return
|
||||
|
||||
const fetchData = async () => {
|
||||
if (!currentStream.peertubeId) return
|
||||
const { peertubeId } = currentStream
|
||||
|
||||
|
||||
const {
|
||||
data: { state, embedPath }
|
||||
} = await axios.get(`https://tv.undersco.re/api/v1/videos/${peertubeId}`)
|
||||
|
||||
setStreamIsLive(state.id === 1)
|
||||
setCurrentStream({ ...currentStream, embedPath })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
}, [])
|
||||
|
||||
useInterval(() => {
|
||||
fetchData()
|
||||
}, streamIsLive ? secondsToMilliseconds(15) : secondsToMilliseconds(1))
|
||||
}
|
@ -8,7 +8,8 @@ import Header, { NavigationModal as MenuModal } from '../../components/Header'
|
||||
import { capitaliseFirstLetter } from '../../helpers/string'
|
||||
import { defaultTheme } from '../../assets/theme'
|
||||
import { ThemedBlock } from './styles'
|
||||
import { useTheme, useUiStore } from '../../store'
|
||||
import { useStreamStore, useTheme, useUiStore } from '../../store'
|
||||
import StreamPreview from '../../components/StreamPreview'
|
||||
|
||||
const Page = ({ children, title = '', description, metaImg, backTo, noindex, withHeader = true, theme = defaultTheme }) => {
|
||||
const { setTheme } = useTheme(store => store)
|
||||
|
@ -14,6 +14,7 @@ import { H1, H2, Span, Label } from '../../components/Text'
|
||||
import Link from '../../components/Link'
|
||||
import Button from '../../components/Button'
|
||||
import { slugify } from '../../helpers/string'
|
||||
import { ButtonsRows } from '../../components/EpisodeCard'
|
||||
|
||||
export const TrailerContainer = styled.div`
|
||||
height: 22em;
|
||||
@ -143,6 +144,8 @@ export const EpisodeCard = ({
|
||||
image,
|
||||
description,
|
||||
beginsOn,
|
||||
endsOn,
|
||||
url,
|
||||
hasPassed,
|
||||
videoUrl,
|
||||
onClickButton,
|
||||
@ -180,15 +183,7 @@ export const EpisodeCard = ({
|
||||
{hasPassed ? (
|
||||
<Button onClick={onClickButton}>{translations.en.watchEpisode}</Button>
|
||||
) : (
|
||||
<a
|
||||
href={
|
||||
hasPassed
|
||||
? videoUrl
|
||||
: `webcal://cloud.undersco.re/remote.php/dav/public-calendars/${config.calendarId}/?export`
|
||||
}
|
||||
>
|
||||
<Button>{translations.en.subEvent}</Button>
|
||||
</a>
|
||||
<ButtonsRows title={title} description={description} beginsOn={beginsOn} endsOn={endsOn} url={url} />
|
||||
)}
|
||||
</VCWrapper>
|
||||
)
|
||||
|
@ -4,6 +4,8 @@ import { defaultTheme } from '../assets/theme'
|
||||
export const useSeriesStore = create((set, get) => ({
|
||||
series: {},
|
||||
episodes: [],
|
||||
|
||||
// Methods
|
||||
setSeries: series => set({ series }),
|
||||
setEpisodes: () => {
|
||||
if (get().series) {
|
||||
@ -16,11 +18,28 @@ export const useSeriesStore = create((set, get) => ({
|
||||
|
||||
export const [useTheme] = create(set => ({
|
||||
theme: defaultTheme,
|
||||
|
||||
// Methods
|
||||
setTheme: (theme) => set({ theme }),
|
||||
setDefaultTheme: () => set({ theme: defaultTheme })
|
||||
}))
|
||||
|
||||
export const [useUiStore] = create((set, get) => ({
|
||||
mobileMenuOpen: false,
|
||||
streamPreviewMinimized: false,
|
||||
streamActive: false,
|
||||
|
||||
// Methods
|
||||
toggleMobileMenu: () => set({ mobileMenuOpen: !get().mobileMenuOpen }),
|
||||
toggleStreamPreviewMinimized: () => set({ streamPreviewMinimized: !get().streamPreviewMinimized }),
|
||||
toggleStreamActive: () => set({ streamActive: !get().streamActive }),
|
||||
}))
|
||||
|
||||
export const [useStreamStore] = create((set) => ({
|
||||
currentStream: null,
|
||||
streamIsLive: false,
|
||||
|
||||
// Methods
|
||||
setCurrentStream: (currentStream) => set({ currentStream }),
|
||||
setStreamIsLive: (streamIsLive) => set({ streamIsLive }),
|
||||
}))
|
Loading…
Reference in New Issue
Block a user