refactor for dynamic routes
This commit is contained in:
parent
fe85d93700
commit
7c63f7538c
9
app.js
9
app.js
@ -1,15 +1,16 @@
|
|||||||
import { h } from 'preact'
|
import { h } from 'preact'
|
||||||
import { useState, useEffect } from 'preact/hooks'
|
import { useState, useEffect } from 'preact/hooks'
|
||||||
import { isWithinInterval, subHours, addHours } from 'date-fns'
|
import { isWithinInterval, subHours, addHours } from 'date-fns'
|
||||||
import { zonedTimeToUtc, utcToZonedTime, format } from 'date-fns-tz'
|
import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'
|
||||||
|
|
||||||
import Video from './src/components/Video'
|
import Video from './src/components/Video'
|
||||||
import config from './src/data/config'
|
|
||||||
import Info from './src/components/Info'
|
import Info from './src/components/Info'
|
||||||
import { useEventCalendar, useEventApi } from './src/hooks/data'
|
import { useEventCalendar, useEventApi } from './src/hooks/data'
|
||||||
import { useTimeout } from './src/hooks/timerHooks'
|
import { useTimeout } from './src/hooks/timerHooks'
|
||||||
|
|
||||||
export default () => {
|
export default (props) => {
|
||||||
|
|
||||||
|
console.log({ props })
|
||||||
const [currentVideo, setCurrentVideo] = useState(null)
|
const [currentVideo, setCurrentVideo] = useState(null)
|
||||||
const [streamIsLive, setStreamIsLive] = useState(false)
|
const [streamIsLive, setStreamIsLive] = useState(false)
|
||||||
const [infoActive, setInfoActive] = useState(false)
|
const [infoActive, setInfoActive] = useState(false)
|
||||||
@ -60,7 +61,7 @@ export default () => {
|
|||||||
) : (
|
) : (
|
||||||
<Info
|
<Info
|
||||||
data={calData}
|
data={calData}
|
||||||
loading={loading || !minLoadTimePassed}
|
loading={false}
|
||||||
infoActive={infoActive}
|
infoActive={infoActive}
|
||||||
currentVideo={currentVideo}
|
currentVideo={currentVideo}
|
||||||
setInfoActive={setInfoActive}
|
setInfoActive={setInfoActive}
|
||||||
|
39
index.js
39
index.js
@ -1,5 +1,42 @@
|
|||||||
import { h, render } from 'preact'
|
import { h, render } from 'preact'
|
||||||
import App from './app'
|
import { useState } from 'preact/hooks'
|
||||||
|
import { BrowserRouter, Route, Switch } from 'react-router-dom'
|
||||||
|
|
||||||
|
import Main from './app'
|
||||||
|
import SeriesPage from './src/pages/SeriesPage'
|
||||||
|
import { slugify } from './src/helpers/string'
|
||||||
|
import { useEventApi, useEventCalendar } from './src/hooks/data'
|
||||||
|
import { useTimeout } from './src/hooks/timerHooks'
|
||||||
|
import LoaderLayout from './src/pages/LoaderLayout'
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const { data: calData, calLoading } = useEventCalendar()
|
||||||
|
const { data: seriesDataArray, loading: eventsLoading } = useEventApi()
|
||||||
|
const [minLoadTimePassed, setMinTimeUp] = useState(false)
|
||||||
|
|
||||||
|
useTimeout(() => {
|
||||||
|
setMinTimeUp(true)
|
||||||
|
}, 1500)
|
||||||
|
|
||||||
|
|
||||||
|
const seriesData = Object.values(seriesDataArray)
|
||||||
|
console.log(seriesData)
|
||||||
|
|
||||||
|
return calLoading || eventsLoading || !minLoadTimePassed ? (
|
||||||
|
<LoaderLayout />
|
||||||
|
) : (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Switch>
|
||||||
|
<Route exact path="/" component={Main} />
|
||||||
|
{seriesData.length ? seriesData.map(series => (
|
||||||
|
<Route exact path={`/series/${slugify(series.name)}`}>
|
||||||
|
<SeriesPage data={series} />
|
||||||
|
</Route>)) : null}
|
||||||
|
</Switch>
|
||||||
|
</BrowserRouter>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const appEl = document.getElementById('app')
|
const appEl = document.getElementById('app')
|
||||||
|
|
||||||
|
@ -19,11 +19,14 @@
|
|||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"date-fns": "^2.19.0",
|
"date-fns": "^2.19.0",
|
||||||
"date-fns-tz": "^1.1.4",
|
"date-fns-tz": "^1.1.4",
|
||||||
|
"dotenv": "^10.0.0",
|
||||||
"ical": "^0.8.0",
|
"ical": "^0.8.0",
|
||||||
"ical.js": "^1.4.0",
|
"ical.js": "^1.4.0",
|
||||||
"markdown-to-jsx": "^7.1.2",
|
"markdown-to-jsx": "^7.1.2",
|
||||||
"preact": "^10.5.12",
|
"preact": "^10.5.12",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
|
"react-router-dom": "^5.3.0",
|
||||||
|
"striptags": "^3.2.0",
|
||||||
"styled-components": "^5.2.1"
|
"styled-components": "^5.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -41,4 +44,4 @@
|
|||||||
"sass": "^1.32.8",
|
"sass": "^1.32.8",
|
||||||
"scss": "^0.2.4"
|
"scss": "^0.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,17 +15,16 @@ import {
|
|||||||
PositionedCross as CrossSvg,
|
PositionedCross as CrossSvg,
|
||||||
Row,
|
Row,
|
||||||
ActionButton as Button,
|
ActionButton as Button,
|
||||||
Img,
|
|
||||||
Trailer,
|
Trailer,
|
||||||
} from './styles'
|
} from './styles'
|
||||||
|
|
||||||
import intro from '../../data/intro.md'
|
import intro from '../../data/intro.md'
|
||||||
import credits from '../../data/credits.md'
|
import credits from '../../data/credits.md'
|
||||||
import { colours } from '../../assets/theme'
|
|
||||||
import config from '../../data/config'
|
import config from '../../data/config'
|
||||||
import trailerThumb from '../../assets/img/main_thumb.png'
|
import trailerThumb from '../../assets/img/main_thumb.png'
|
||||||
|
|
||||||
const Info = ({ data, loading, infoActive, setInfoActive, currentVideo }) => {
|
const Info = ({ data }) => {
|
||||||
const trailerUrl = `https://www.youtube-nocookie.com/embed/${config.seriesTrailerId}?autoplay=1&vq=hd1080`
|
const trailerUrl = `https://www.youtube-nocookie.com/embed/${config.seriesTrailerId}?autoplay=1&vq=hd1080`
|
||||||
const [embedURL, setEmbedUrl] = useState('')
|
const [embedURL, setEmbedUrl] = useState('')
|
||||||
|
|
||||||
@ -36,26 +35,26 @@ const Info = ({ data, loading, infoActive, setInfoActive, currentVideo }) => {
|
|||||||
setEmbedUrl('')
|
setEmbedUrl('')
|
||||||
}
|
}
|
||||||
|
|
||||||
const pastStreams =
|
// const pastStreams =
|
||||||
data && data.length
|
// data && data.length
|
||||||
? data.filter(feeditem => isPast(new Date(feeditem.end)))
|
// ? data.filter(feeditem => isPast(new Date(feeditem.end)))
|
||||||
: []
|
// : []
|
||||||
|
|
||||||
const futureStreams =
|
// const futureStreams =
|
||||||
data && data.length
|
// data && data.length
|
||||||
? data
|
// ? data
|
||||||
.filter(
|
// .filter(
|
||||||
feeditem =>
|
// feeditem =>
|
||||||
feeditem.id !== (currentVideo && currentVideo.id) &&
|
// feeditem.id !== (currentVideo && currentVideo.id) &&
|
||||||
isFuture(new Date(feeditem.start))
|
// isFuture(new Date(feeditem.start))
|
||||||
)
|
// )
|
||||||
.sort(
|
// .sort(
|
||||||
(a, b) =>
|
// (a, b) =>
|
||||||
// Turn your strings into dates, and then subtract them
|
// // Turn your strings into dates, and then subtract them
|
||||||
// to get a value that is either negative, positive, or zero.
|
// // to get a value that is either negative, positive, or zero.
|
||||||
new Date(a.start) - new Date(b.start)
|
// new Date(a.start) - new Date(b.start)
|
||||||
)
|
// )
|
||||||
: []
|
// : []
|
||||||
|
|
||||||
const dateString = `${new Date()}`
|
const dateString = `${new Date()}`
|
||||||
let tzShort =
|
let tzShort =
|
||||||
@ -71,79 +70,75 @@ const Info = ({ data, loading, infoActive, setInfoActive, currentVideo }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InfoLayout loading={loading}>
|
<InfoLayout>
|
||||||
{embedURL ? (
|
{/* {embedURL ? (
|
||||||
<VideoEmbed onClose={deactivateEmbed} url={embedURL} />
|
<VideoEmbed onClose={deactivateEmbed} url={embedURL} />
|
||||||
) : null}
|
) : null} */}
|
||||||
{infoActive && (
|
<Fragment>
|
||||||
<CrossSvg size="64" onClick={() => setInfoActive(false)} />
|
<InfoContent>
|
||||||
)}
|
<H1>The Para-Real:</H1>
|
||||||
{!loading && (
|
<H1>Finding the Future in Unexpected Places</H1>
|
||||||
<Fragment>
|
<Markdown withLinebreaks>{intro}</Markdown>
|
||||||
<InfoContent>
|
|
||||||
<H1>The Para-Real:</H1>
|
|
||||||
<H1>Finding the Future in Unexpected Places</H1>
|
|
||||||
<Markdown withLinebreaks>{intro}</Markdown>
|
|
||||||
|
|
||||||
<Trailer imgSrc={trailerThumb} onClick={onClickTrailerButton} />
|
<Trailer imgSrc={trailerThumb} onClick={onClickTrailerButton} />
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<a
|
<a
|
||||||
href="https://discord.gg/Xu9D3qVana"
|
href="https://discord.gg/Xu9D3qVana"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<Button>Join the Discord</Button>
|
<Button>Join the Discord</Button>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://ndc.substack.com/subscribe"
|
href="https://ndc.substack.com/subscribe"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<Button>Subscribe to the mailing list</Button>
|
<Button>Subscribe to the mailing list</Button>
|
||||||
</a>
|
</a>
|
||||||
</Row>
|
</Row>
|
||||||
</InfoContent>
|
</InfoContent>
|
||||||
{currentVideo && (
|
{/* {currentVideo && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Title>{translations.en.nowPlaying}:</Title>
|
<Title>{translations.en.nowPlaying}:</Title>
|
||||||
<VideoCard {...currentVideo} />
|
<VideoCard {...currentVideo} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{futureStreams.length ? (
|
{futureStreams.length ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Title>{translations.en.nextStream}:</Title>
|
<Title>{translations.en.nextStream}:</Title>
|
||||||
{futureStreams.map(feeditem => (
|
{futureStreams.map(feeditem => (
|
||||||
<VideoCard
|
<VideoCard
|
||||||
key={feeditem.start}
|
key={feeditem.start}
|
||||||
tzShort={tzShort}
|
tzShort={tzShort}
|
||||||
{...feeditem}
|
{...feeditem}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{pastStreams.length ? (
|
||||||
|
<Fragment>
|
||||||
|
<Title>{translations.en.pastStream}:</Title>
|
||||||
|
{pastStreams.map(feeditem => (
|
||||||
|
<VideoCard
|
||||||
|
key={feeditem.start}
|
||||||
|
hasPassed
|
||||||
|
onClickButton={() =>
|
||||||
|
setEmbedUrl(`${config.peertube_root}${feeditem.embedPath}`)
|
||||||
|
}
|
||||||
|
{...feeditem}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
) : null} */}
|
||||||
|
{/* <InfoContent>
|
||||||
|
<Markdown>{credits}</Markdown>
|
||||||
|
</InfoContent> */}
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
{pastStreams.length ? (
|
|
||||||
<Fragment>
|
|
||||||
<Title>{translations.en.pastStream}:</Title>
|
|
||||||
{pastStreams.map(feeditem => (
|
|
||||||
<VideoCard
|
|
||||||
key={feeditem.start}
|
|
||||||
hasPassed
|
|
||||||
onClickButton={() =>
|
|
||||||
setEmbedUrl(`${config.peertube_root}${feeditem.embedPath}`)
|
|
||||||
}
|
|
||||||
{...feeditem}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Fragment>
|
|
||||||
) : null}
|
|
||||||
<InfoContent>
|
|
||||||
<Markdown>{credits}</Markdown>
|
|
||||||
</InfoContent>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</InfoLayout>
|
</InfoLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import Loader from '../Loader'
|
|||||||
import { useTimeout } from '../../hooks/timerHooks'
|
import { useTimeout } from '../../hooks/timerHooks'
|
||||||
import { NdcLogo, RFLogo } from '../Logo'
|
import { NdcLogo, RFLogo } from '../Logo'
|
||||||
|
|
||||||
const InfoLayout = ({ children, loading }) => (
|
const InfoLayout = ({ title, subtitle, children, loading }) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Content>
|
<Content>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
@ -30,15 +30,13 @@ const InfoLayout = ({ children, loading }) => (
|
|||||||
</Content>
|
</Content>
|
||||||
<Hero>
|
<Hero>
|
||||||
<div>
|
<div>
|
||||||
<H1>The</H1>
|
<H1>{title}</H1>
|
||||||
<H1>Para-</H1>
|
|
||||||
<H1>Real</H1>
|
|
||||||
<H1
|
<H1
|
||||||
css={`
|
css={`
|
||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Finding the Future in Unexpected Places
|
{subtitle}
|
||||||
</H1>
|
</H1>
|
||||||
</div>
|
</div>
|
||||||
<TaglineContainer>
|
<TaglineContainer>
|
||||||
|
@ -39,10 +39,8 @@ export const Top = styled.div`
|
|||||||
const gradientColourLight = '#F8E5E2'
|
const gradientColourLight = '#F8E5E2'
|
||||||
const gradientColourDark = colours.midnightDarker
|
const gradientColourDark = colours.midnightDarker
|
||||||
const getGradient = (direction, lightDark) =>
|
const getGradient = (direction, lightDark) =>
|
||||||
`linear-gradient(to ${direction}, ${
|
`linear-gradient(to ${direction}, ${lightDark === 'dark' ? gradientColourDark : gradientColourLight
|
||||||
lightDark === 'dark' ? gradientColourDark : gradientColourLight
|
}ee 0%,${lightDark === 'dark' ? gradientColourDark : gradientColourLight
|
||||||
}ee 0%,${
|
|
||||||
lightDark === 'dark' ? gradientColourDark : gradientColourLight
|
|
||||||
}00 100%);`
|
}00 100%);`
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@ -78,7 +76,7 @@ export const Hero = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1:not(:last-of-type) {
|
h1:not(:last-of-type) {
|
||||||
font-size: 30vh;
|
font-size: 15vw;
|
||||||
|
|
||||||
@media screen and (max-height: 600px) {
|
@media screen and (max-height: 600px) {
|
||||||
font-size: 20vh;
|
font-size: 20vh;
|
||||||
|
@ -4,16 +4,18 @@ import { useInterval, useTimeout } from '../../hooks/timerHooks'
|
|||||||
import { colours } from '../../assets/theme'
|
import { colours } from '../../assets/theme'
|
||||||
import { H1 } from '../Text'
|
import { H1 } from '../Text'
|
||||||
|
|
||||||
|
const defaultLoader = [':..', '.:.', '..:', '...']
|
||||||
// const symbols = ['⌏', '⌎', '⌌', '⌍']
|
// const symbols = ['⌏', '⌎', '⌌', '⌍']
|
||||||
|
|
||||||
const Loader = ({
|
const Loader = ({
|
||||||
active = true,
|
active = true,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
animation = [':..', '.:.', '..:', '...'],
|
animation = defaultLoader,
|
||||||
|
colour = colours.rose,
|
||||||
}) => {
|
}) => {
|
||||||
const [text, setText] = useState('.')
|
const [text, setText] = useState('.')
|
||||||
const arrayPosition = useRef(offset)
|
const arrayPosition = useRef(offset)
|
||||||
const rate = 350
|
const rate = 300
|
||||||
|
|
||||||
useInterval(
|
useInterval(
|
||||||
() => {
|
() => {
|
||||||
@ -29,7 +31,7 @@ const Loader = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<H1 as="span" colour={colours.midnightDarker}>
|
<H1 as="span" colour={colour}>
|
||||||
{text}
|
{text}
|
||||||
</H1>
|
</H1>
|
||||||
)
|
)
|
||||||
|
11
src/helpers/environment.js
Normal file
11
src/helpers/environment.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/* eslint-disable indent */
|
||||||
|
/* NOT WORKING YET */
|
||||||
|
export default {
|
||||||
|
PEERTUBE_ROOT: process.env.PEERTUBE_ROOT,
|
||||||
|
EVENTS_API_URL: process.env.EVENTS_API_URL,
|
||||||
|
SERIES_TRAILER_ID: process.env.SERIES_TRAILER_ID,
|
||||||
|
CALENDAR_ID: process.env.CALENDAR_ID,
|
||||||
|
CHAT_GUILD_ID: process.env.CHAT_GUILD_ID,
|
||||||
|
CHAT_CHANNEL_ID: process.env.CHAT_CHANNEL_ID,
|
||||||
|
CHAT_CSS: process.env.CHAT_CSS,
|
||||||
|
}
|
19
src/helpers/string.js
Normal file
19
src/helpers/string.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export const slugify = (title) => {
|
||||||
|
let str = title.replace(/^\s+|\s+$/g, '') // trim
|
||||||
|
str = str.toLowerCase()
|
||||||
|
|
||||||
|
// remove accents, swap ñ for n, etc
|
||||||
|
const from = 'àáäâèéëêìíïîòóöôùúüûñç·/_,:;'
|
||||||
|
const to = 'aaaaeeeeiiiioooouuuunc------'
|
||||||
|
for (let i = 0, l = from.length; i < l; i++) {
|
||||||
|
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str
|
||||||
|
.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
|
||||||
|
.replace(/\s+/g, '-') // collapse whitespace and replace by -
|
||||||
|
.replace(/-+/g, '-') // collapse dashes
|
||||||
|
|
||||||
|
return str
|
||||||
|
|
||||||
|
}
|
30
src/pages/LoaderLayout/index.js
Normal file
30
src/pages/LoaderLayout/index.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { h } from 'preact'
|
||||||
|
|
||||||
|
import translations from '../../data/strings'
|
||||||
|
import { H1 } from '../../components/Text'
|
||||||
|
import {
|
||||||
|
Wrapper,
|
||||||
|
LoaderWrapper,
|
||||||
|
Hero,
|
||||||
|
PositionedLogo as Logo,
|
||||||
|
TaglineContainer,
|
||||||
|
} from './styles'
|
||||||
|
import Loader from '../../components/Loader'
|
||||||
|
|
||||||
|
const LoaderLayout = () => (
|
||||||
|
<Wrapper>
|
||||||
|
<Logo active />
|
||||||
|
<LoaderWrapper>
|
||||||
|
<Loader />
|
||||||
|
</LoaderWrapper>
|
||||||
|
<Hero />
|
||||||
|
<TaglineContainer>
|
||||||
|
{translations &&
|
||||||
|
translations.en.underscoreTagline.map(line => (
|
||||||
|
<H1 key={line}>{line}</H1>
|
||||||
|
))}
|
||||||
|
</TaglineContainer>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default LoaderLayout
|
151
src/pages/LoaderLayout/styles.js
Normal file
151
src/pages/LoaderLayout/styles.js
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
import bg from '../../assets/img/hero/1lg.png'
|
||||||
|
|
||||||
|
// import { H1 } from '../../components/Text'
|
||||||
|
|
||||||
|
import Logo from '../../components/Logo'
|
||||||
|
|
||||||
|
const heroWidth = '66vw'
|
||||||
|
|
||||||
|
export const Wrapper = styled.div`
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
padding: 2em;
|
||||||
|
display: flex;
|
||||||
|
background-color: ${colours.midnightDarker};
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: fixed;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
p,
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
color: ${colours.midnightDarker};
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1200px) {
|
||||||
|
padding: 1.5em;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Top = styled.div`
|
||||||
|
width: 50%;
|
||||||
|
`
|
||||||
|
|
||||||
|
const gradientColourLight = '#F8E5E2'
|
||||||
|
const gradientColourDark = colours.midnightDarker
|
||||||
|
const getGradient = (direction, lightDark) =>
|
||||||
|
`linear-gradient(to ${direction}, ${lightDark === 'dark' ? gradientColourDark : gradientColourLight
|
||||||
|
}ee 0%,${lightDark === 'dark' ? gradientColourDark : gradientColourLight
|
||||||
|
}00 100%);`
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
export const Fade = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
background-color: linear;
|
||||||
|
position: fixed;
|
||||||
|
padding: 2em 0 1em 2em;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: ${getGradient('bottom')};
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Hero = styled.div`
|
||||||
|
width: ${heroWidth};
|
||||||
|
height: 100vh;
|
||||||
|
background: url(${bg});
|
||||||
|
background-size: cover;
|
||||||
|
background-position-x: right;
|
||||||
|
background-position-y: 60%;
|
||||||
|
position: fixed;
|
||||||
|
padding: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
padding: 2em 2em 8px 2em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
/*
|
||||||
|
h1:not(:last-of-type) {
|
||||||
|
font-size: 30vh;
|
||||||
|
|
||||||
|
@media screen and (max-height: 600px) {
|
||||||
|
font-size: 20vh;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 1200px) {
|
||||||
|
font-size: 20vh;
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const LoaderWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 33vw;
|
||||||
|
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Content = styled.div`
|
||||||
|
/* margin-bottom: 3em; */
|
||||||
|
`
|
||||||
|
|
||||||
|
export const PositionedLogo = styled(Logo)`
|
||||||
|
position: fixed;
|
||||||
|
top: 2em;
|
||||||
|
left: 3em;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const FadeBottom = styled.div`
|
||||||
|
background: ${getGradient('top', 'dark')};
|
||||||
|
width: ${heroWidth};
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
right: 0;
|
||||||
|
/* left: 0; */
|
||||||
|
pointer-events: none;
|
||||||
|
min-height: 75px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const TaglineContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0em;
|
||||||
|
padding-bottom: 0;
|
||||||
|
right: 1em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
h1 {
|
||||||
|
color: ${colours.rose};
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
149
src/pages/SeriesPage/index.js
Normal file
149
src/pages/SeriesPage/index.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
|
import { h, Fragment } from 'preact'
|
||||||
|
import { useState, useEffect } from 'preact/hooks'
|
||||||
|
import { isFuture, isPast } from 'date-fns'
|
||||||
|
import striptags from 'striptags'
|
||||||
|
|
||||||
|
import { H1 } from '../../components/Text'
|
||||||
|
import Markdown from '../../components/Markdown'
|
||||||
|
// import translations from '../../data/strings'
|
||||||
|
import InfoLayout from '../../components/InfoLayout'
|
||||||
|
import VideoEmbed from '../../components/VideoEmbed'
|
||||||
|
import {
|
||||||
|
VideoCard,
|
||||||
|
Title,
|
||||||
|
InfoContent,
|
||||||
|
PositionedCross as CrossSvg,
|
||||||
|
Row,
|
||||||
|
ActionButton as Button,
|
||||||
|
Trailer,
|
||||||
|
} from './styles'
|
||||||
|
|
||||||
|
import intro from '../../data/intro.md'
|
||||||
|
// import credits from '../../data/credits.md'
|
||||||
|
|
||||||
|
import config from '../../data/config'
|
||||||
|
import trailerThumb from '../../assets/img/main_thumb.png'
|
||||||
|
|
||||||
|
const SeriesPage = ({ data }) => {
|
||||||
|
const trailerUrl = `https://www.youtube-nocookie.com/embed/${config.seriesTrailerId}?autoplay=1&vq=hd1080`
|
||||||
|
const [embedURL, setEmbedUrl] = useState('')
|
||||||
|
|
||||||
|
const onClickTrailerButton = () => {
|
||||||
|
setEmbedUrl(trailerUrl)
|
||||||
|
}
|
||||||
|
const deactivateEmbed = () => {
|
||||||
|
setEmbedUrl('')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log({ data })
|
||||||
|
|
||||||
|
// const pastStreams =
|
||||||
|
// data && data.length
|
||||||
|
// ? data.filter(feeditem => isPast(new Date(feeditem.end)))
|
||||||
|
// : []
|
||||||
|
|
||||||
|
// const futureStreams =
|
||||||
|
// data && data.length
|
||||||
|
// ? data
|
||||||
|
// .filter(
|
||||||
|
// feeditem =>
|
||||||
|
// feeditem.id !== (currentVideo && currentVideo.id) &&
|
||||||
|
// isFuture(new Date(feeditem.start))
|
||||||
|
// )
|
||||||
|
// .sort(
|
||||||
|
// (a, b) =>
|
||||||
|
// // Turn your strings into dates, and then subtract them
|
||||||
|
// // to get a value that is either negative, positive, or zero.
|
||||||
|
// new Date(a.start) - new Date(b.start)
|
||||||
|
// )
|
||||||
|
// : []
|
||||||
|
|
||||||
|
const dateString = `${new Date()}`
|
||||||
|
let tzShort =
|
||||||
|
// Works for the majority of modern browsers
|
||||||
|
dateString.match(/\(([^\)]+)\)$/) ||
|
||||||
|
// IE outputs date strings in a different format:
|
||||||
|
dateString.match(/([A-Z]+) [\d]{4}$/)
|
||||||
|
|
||||||
|
if (tzShort) {
|
||||||
|
// Old Firefox uses the long timezone name (e.g., "Central
|
||||||
|
// Daylight Time" instead of "CDT")
|
||||||
|
tzShort = tzShort[1].match(/[A-Z]/g).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InfoLayout title={data.name} subtitle={striptags(data.summary)}>
|
||||||
|
{embedURL ? (
|
||||||
|
<VideoEmbed onClose={deactivateEmbed} url={embedURL} />
|
||||||
|
) : null}
|
||||||
|
<Fragment>
|
||||||
|
<InfoContent>
|
||||||
|
<H1>{data.name}:</H1>
|
||||||
|
<H1>{data.summary}</H1>
|
||||||
|
<Markdown withLinebreaks>{intro}</Markdown>
|
||||||
|
|
||||||
|
<Trailer imgSrc={trailerThumb} onClick={onClickTrailerButton} />
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/Xu9D3qVana"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Button>Join the Discord</Button>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://ndc.substack.com/subscribe"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Button>Subscribe to the mailing list</Button>
|
||||||
|
</a>
|
||||||
|
</Row>
|
||||||
|
</InfoContent>
|
||||||
|
{/* {currentVideo && (
|
||||||
|
<Fragment>
|
||||||
|
<Title>{translations.en.nowPlaying}:</Title>
|
||||||
|
<VideoCard {...currentVideo} />
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{futureStreams.length ? (
|
||||||
|
<Fragment>
|
||||||
|
<Title>{translations.en.nextStream}:</Title>
|
||||||
|
{futureStreams.map(feeditem => (
|
||||||
|
<VideoCard
|
||||||
|
key={feeditem.start}
|
||||||
|
tzShort={tzShort}
|
||||||
|
{...feeditem}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{pastStreams.length ? (
|
||||||
|
<Fragment>
|
||||||
|
<Title>{translations.en.pastStream}:</Title>
|
||||||
|
{pastStreams.map(feeditem => (
|
||||||
|
<VideoCard
|
||||||
|
key={feeditem.start}
|
||||||
|
hasPassed
|
||||||
|
onClickButton={() =>
|
||||||
|
setEmbedUrl(`${config.peertube_root}${feeditem.embedPath}`)
|
||||||
|
}
|
||||||
|
{...feeditem}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
) : null} */}
|
||||||
|
{/* <InfoContent>
|
||||||
|
<Markdown>{credits}</Markdown>
|
||||||
|
</InfoContent> */}
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
</InfoLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SeriesPage
|
213
src/pages/SeriesPage/styles.js
Normal file
213
src/pages/SeriesPage/styles.js
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { h, Fragment } from 'preact'
|
||||||
|
import { zonedTimeToUtc, utcToZonedTime, format } from 'date-fns-tz'
|
||||||
|
import { bool, instanceOf, string } from 'prop-types'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { colours, textSizes } from '../../assets/theme'
|
||||||
|
import config from '../../data/config'
|
||||||
|
import Logo from '../../components/Logo'
|
||||||
|
import translations from '../../data/strings'
|
||||||
|
import CrossSvg from '../../components/Svg/Cross'
|
||||||
|
import PlaySvg from '../../components/Svg/Play'
|
||||||
|
|
||||||
|
import { P, H1, H2, Span, Label } from '../../components/Text'
|
||||||
|
import { Link } from '../../components/Link'
|
||||||
|
import Button from '../../components/Button'
|
||||||
|
|
||||||
|
export const TrailerContainer = styled.div`
|
||||||
|
background: url(${props => props.imgSrc});
|
||||||
|
height: 20em;
|
||||||
|
background-size: cover;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid ${colours.midnightDarker};
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
padding: 1em 2em;
|
||||||
|
background-color: #ffffffba;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: ${colours.midnightDarker};
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:hover div {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Trailer = props => (
|
||||||
|
<TrailerContainer {...props}>
|
||||||
|
<div>
|
||||||
|
<PlaySvg colour={colours.midnightDarker} size="20" />
|
||||||
|
<Label>{translations.en.watchTrailer}</Label>
|
||||||
|
</div>
|
||||||
|
</TrailerContainer>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ActionButton = styled(Button)`
|
||||||
|
font-size: 18px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Row = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
width: 50%;
|
||||||
|
&:not(:last-of-type) {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const InfoContent = styled.div`
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 0 0em 2px;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const PositionedLogo = styled(Logo)`
|
||||||
|
margin-bottom: 64px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const TaglineContainer = styled.div`
|
||||||
|
h1 {
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Title = styled(H1)`
|
||||||
|
margin: 0.3em 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const PositionedCross = styled(CrossSvg)`
|
||||||
|
position: fixed;
|
||||||
|
right: 2.5em;
|
||||||
|
top: 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
stroke: ${colours.midnightDarker};
|
||||||
|
z-index: 5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const VCWrapper = styled.div`
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 0 6em 2px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const VCImg = styled.img`
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ItemTitleWrapper = styled.div`
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
`
|
||||||
|
|
||||||
|
const DateLabel = styled(Label)`
|
||||||
|
margin: 1em 0;
|
||||||
|
display: block;
|
||||||
|
`
|
||||||
|
|
||||||
|
const LinkBlock = styled(Link)`
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
|
const renderTitles = titles =>
|
||||||
|
titles.split('\\n').map(title => <H2 key={title}>{title}</H2>)
|
||||||
|
|
||||||
|
export const VideoCard = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
start,
|
||||||
|
previewPath,
|
||||||
|
hasPassed,
|
||||||
|
videoUrl,
|
||||||
|
onClickButton,
|
||||||
|
tzShort,
|
||||||
|
}) => {
|
||||||
|
const startDate = new Date(start)
|
||||||
|
const utcDate = zonedTimeToUtc(startDate, 'Europe/Berlin')
|
||||||
|
|
||||||
|
const { timeZone } = Intl.DateTimeFormat().resolvedOptions()
|
||||||
|
const zonedDate = utcToZonedTime(utcDate, timeZone)
|
||||||
|
return (
|
||||||
|
<VCWrapper>
|
||||||
|
<DateLabel colour={colours.midnight} size={textSizes.lg}>
|
||||||
|
{`${hasPassed ? translations.en.streamDatePast : ''}`}
|
||||||
|
<Span bold colour={colours.midnight}>
|
||||||
|
{hasPassed
|
||||||
|
? format(zonedDate, 'dd/MM/yy')
|
||||||
|
: `${format(zonedDate, 'do LLLL y // HH:mm')} ${tzShort}`}
|
||||||
|
</Span>
|
||||||
|
</DateLabel>
|
||||||
|
{videoUrl && hasPassed ? (
|
||||||
|
<LinkBlock href={videoUrl}>
|
||||||
|
<ItemTitleWrapper>{renderTitles(title)}</ItemTitleWrapper>
|
||||||
|
<VCImg src={`${config.peertube_root}${previewPath}`} alt="" />
|
||||||
|
</LinkBlock>
|
||||||
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
<ItemTitleWrapper>{renderTitles(title)}</ItemTitleWrapper>
|
||||||
|
<VCImg src={`${config.peertube_root}${previewPath}`} alt="" />
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
<P>{description}</P>
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
</VCWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoCard.propTypes = {
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
start: instanceOf(Date),
|
||||||
|
previewPath: string,
|
||||||
|
hasPassed: bool,
|
||||||
|
videoUrl: string,
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user