Finished off Series-page,
This commit is contained in:
parent
a3bea12645
commit
84ea28cd7d
102
app.js
102
app.js
@ -1,72 +1,46 @@
|
|||||||
import { h } from 'preact'
|
import { h } from 'preact'
|
||||||
import { useState, useEffect } from 'preact/hooks'
|
|
||||||
import { isWithinInterval, subHours, addHours } from 'date-fns'
|
|
||||||
import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'
|
|
||||||
|
|
||||||
import Video from './src/components/Video'
|
import { H1 } from './src/components/Text'
|
||||||
import Info from './src/components/Info'
|
import Page from './src/layouts/Page'
|
||||||
import { useEventCalendar, useEventApi } from './src/hooks/data'
|
|
||||||
import { useTimeout } from './src/hooks/timerHooks'
|
|
||||||
|
|
||||||
export default (props) => {
|
export default () => (
|
||||||
|
<Page>
|
||||||
|
<H1>LANGING PAGE</H1>
|
||||||
|
</Page >
|
||||||
|
)
|
||||||
|
|
||||||
// console.log({ props })
|
|
||||||
const [currentVideo, setCurrentVideo] = useState(null)
|
|
||||||
const [streamIsLive, setStreamIsLive] = useState(false)
|
|
||||||
const [infoActive, setInfoActive] = useState(false)
|
|
||||||
const [minLoadTimePassed, setMinTimeUp] = useState(false)
|
|
||||||
const { data: calData, loading } = useEventCalendar()
|
|
||||||
const { data: eventsData, loading: eventsLoading } = useEventApi()
|
|
||||||
|
|
||||||
useTimeout(() => {
|
|
||||||
setMinTimeUp(true)
|
|
||||||
}, 1500)
|
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (calData && calData.length) {
|
// if (calData && calData.length) {
|
||||||
calData.forEach((stream, index) => {
|
// calData.forEach((stream, index) => {
|
||||||
const utcStartDate = zonedTimeToUtc(
|
// const utcStartDate = zonedTimeToUtc(
|
||||||
new Date(stream.start),
|
// new Date(stream.start),
|
||||||
'Europe/Berlin'
|
// 'Europe/Berlin'
|
||||||
)
|
// )
|
||||||
const utcEndDate = zonedTimeToUtc(new Date(stream.end), 'Europe/Berlin')
|
// const utcEndDate = zonedTimeToUtc(new Date(stream.end), 'Europe/Berlin')
|
||||||
const { timeZone } = Intl.DateTimeFormat().resolvedOptions()
|
// 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),
|
||||||
|
// })
|
||||||
|
// ) {
|
||||||
|
// setCurrentVideo(stream)
|
||||||
|
// }
|
||||||
|
// if (
|
||||||
|
// isWithinInterval(new Date(), {
|
||||||
|
// start: zonedStartDate,
|
||||||
|
// end: zonedEndDate,
|
||||||
|
// })
|
||||||
|
// ) {
|
||||||
|
// setStreamIsLive(true)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }, [calData, eventsData, eventsLoading])
|
||||||
|
|
||||||
const zonedStartDate = utcToZonedTime(utcStartDate, timeZone)
|
|
||||||
const zonedEndDate = utcToZonedTime(utcEndDate, timeZone)
|
|
||||||
if (
|
|
||||||
isWithinInterval(new Date(), {
|
|
||||||
start: subHours(zonedStartDate, 1),
|
|
||||||
end: addHours(zonedEndDate, 1),
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
setCurrentVideo(stream)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
isWithinInterval(new Date(), {
|
|
||||||
start: zonedStartDate,
|
|
||||||
end: zonedEndDate,
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
setStreamIsLive(true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [calData, eventsData, eventsLoading])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{currentVideo && !infoActive && minLoadTimePassed ? (
|
|
||||||
<Video video={currentVideo} setInfoActive={setInfoActive} />
|
|
||||||
) : (
|
|
||||||
<Info
|
|
||||||
data={calData}
|
|
||||||
loading={false}
|
|
||||||
infoActive={infoActive}
|
|
||||||
currentVideo={currentVideo}
|
|
||||||
setInfoActive={setInfoActive}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>The Para-Real: Finding the Future in Unexpected Places</title>
|
<title>Underscore Streams</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"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-helmet": "^6.1.0",
|
||||||
"react-router-dom": "^5.3.0",
|
"react-router-dom": "^5.3.0",
|
||||||
"striptags": "^3.2.0",
|
"striptags": "^3.2.0",
|
||||||
"styled-components": "^5.2.1",
|
"styled-components": "^5.2.1",
|
||||||
|
@ -26,8 +26,13 @@ export const textSizes = {
|
|||||||
hg: 200,
|
hg: 200,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const defaultTheme = {
|
||||||
|
background: colours.rose, foreground: colours.midnight, highlight: colours.highlight
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
colours,
|
colours,
|
||||||
textSizes,
|
textSizes,
|
||||||
ui,
|
ui,
|
||||||
|
defaultTheme
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@ import { colours } from '../../assets/theme'
|
|||||||
|
|
||||||
const Button = styled.button`
|
const Button = styled.button`
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 1px solid ${colours.midnightDarker};
|
border: 1px solid ${colours.rose};
|
||||||
padding: 0.3em 1em;
|
padding: 0.3em 1em;
|
||||||
font-family: Karla;
|
font-family: Karla;
|
||||||
font-weight: inherit;
|
font-weight: inherit;
|
||||||
color: ${colours.midnightDarker};
|
color: ${colours.rose};
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
@ -21,8 +21,8 @@ const Button = styled.button`
|
|||||||
}
|
}
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
background-color: ${colours.midnightDarker};
|
background-color: ${colours.rose};
|
||||||
color: ${colours.roseLight};
|
color: ${colours.midnightDarker};
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
path {
|
path {
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export const sortData = data =>
|
|
||||||
Object.values(data)
|
|
||||||
.filter(feedItem => feedItem.type === 'VEVENT')
|
|
||||||
.sort((a, b) => new Date(a.start) - new Date(b.start))
|
|
@ -1,146 +0,0 @@
|
|||||||
/* eslint-disable react/prop-types */
|
|
||||||
import { h, Fragment } from 'preact'
|
|
||||||
import { useState, useEffect } from 'preact/hooks'
|
|
||||||
import { isFuture, isPast } from 'date-fns'
|
|
||||||
|
|
||||||
import { H1 } from '../Text'
|
|
||||||
import Markdown from '../Markdown'
|
|
||||||
import translations from '../../data/strings'
|
|
||||||
import InfoLayout from '../InfoLayout'
|
|
||||||
import VideoEmbed from '../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 Info = ({ 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('')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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>
|
|
||||||
{/* {embedURL ? (
|
|
||||||
<VideoEmbed onClose={deactivateEmbed} url={embedURL} />
|
|
||||||
) : null} */}
|
|
||||||
<Fragment>
|
|
||||||
<InfoContent>
|
|
||||||
<H1>The Para-Real:</H1>
|
|
||||||
<H1>Finding the Future in Unexpected Places</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 Info
|
|
@ -1,240 +0,0 @@
|
|||||||
import { h, Fragment } from 'preact'
|
|
||||||
import { zonedTimeToUtc, utcToZonedTime, format } from 'date-fns-tz'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { colours, textSizes } from '../../assets/theme'
|
|
||||||
import config from '../../data/config'
|
|
||||||
import Logo from '../Logo'
|
|
||||||
import translations from '../../data/strings'
|
|
||||||
import CrossSvg from '../Svg/Cross'
|
|
||||||
import PlaySvg from '../Svg/Play'
|
|
||||||
|
|
||||||
import { P, H1, H2, Span, Label } from '../Text'
|
|
||||||
import Link from '../Link'
|
|
||||||
import { bool, instanceOf, string } from 'prop-types'
|
|
||||||
import Markdown from '../Markdown'
|
|
||||||
import Button from '../Button'
|
|
||||||
|
|
||||||
export const Wrapper = styled.div`
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
padding: 2em;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
background: url(https://images.unsplash.com/photo-1579762715118-a6f1d4b934f1?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=3031&q=80)
|
|
||||||
${colours.rose};
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-size: cover;
|
|
||||||
background-position-y: 50%;
|
|
||||||
background-position-x: 20vw;
|
|
||||||
background-blend-mode: soft-light;
|
|
||||||
|
|
||||||
p,
|
|
||||||
h1,
|
|
||||||
h2 {
|
|
||||||
color: ${colours.midnightDarker};
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const TrailerContainer = styled.div`
|
|
||||||
background: url(${props => props.imgSrc});
|
|
||||||
/* width: 100%; */
|
|
||||||
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;
|
|
||||||
/* width: 32px; */
|
|
||||||
/* height: 32px; */
|
|
||||||
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,
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
import { h } from 'preact'
|
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
|
||||||
import { bool, string } from 'prop-types'
|
|
||||||
|
|
||||||
import { H1, H2, Label, Span } from '../Text'
|
|
||||||
import {
|
|
||||||
Wrapper,
|
|
||||||
TaglineContainer,
|
|
||||||
LoaderWrapper,
|
|
||||||
Content,
|
|
||||||
Hero,
|
|
||||||
FadeBottom,
|
|
||||||
} from './styles'
|
|
||||||
import translations from '../../data/strings'
|
|
||||||
import { colours } from '../../assets/theme'
|
|
||||||
import Loader from '../Loader'
|
|
||||||
import { useTimeout } from '../../hooks/timerHooks'
|
|
||||||
import { NdcLogo, RFLogo } from '../Logo'
|
|
||||||
|
|
||||||
const InfoLayout = ({ title, subtitle, image, children }) => (
|
|
||||||
<Wrapper>
|
|
||||||
<Content>
|
|
||||||
{children}
|
|
||||||
</Content>
|
|
||||||
<Hero image={image}>
|
|
||||||
<div>
|
|
||||||
<H1>{title}</H1>
|
|
||||||
<H1
|
|
||||||
css={`
|
|
||||||
max-width: 50%;
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{subtitle}
|
|
||||||
</H1>
|
|
||||||
</div>
|
|
||||||
<TaglineContainer>
|
|
||||||
<a href="https://newdesigncongress.org/">
|
|
||||||
<NdcLogo active colour={colours.offwhite} />
|
|
||||||
</a>
|
|
||||||
<Label size="24">{'//'}</Label>
|
|
||||||
<a href="https://reclaimfutures.org/">
|
|
||||||
<RFLogo active colour={colours.offwhite} />
|
|
||||||
</a>
|
|
||||||
</TaglineContainer>
|
|
||||||
<FadeBottom />
|
|
||||||
</Hero>
|
|
||||||
</Wrapper>
|
|
||||||
)
|
|
||||||
|
|
||||||
InfoLayout.propTypes = {
|
|
||||||
loading: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InfoLayout
|
|
81
src/components/Seo/index.js
Normal file
81
src/components/Seo/index.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { h } from 'preact'
|
||||||
|
import { bool, string } from 'prop-types'
|
||||||
|
import Helmet from 'react-helmet'
|
||||||
|
|
||||||
|
const siteTitle = 'Underscore Streams'
|
||||||
|
|
||||||
|
function SEO({ description, title, metaImg: imgSrc, noindex }) {
|
||||||
|
return (
|
||||||
|
<Helmet
|
||||||
|
htmlAttributes={{
|
||||||
|
lang: 'en',
|
||||||
|
}}
|
||||||
|
title={title}
|
||||||
|
titleTemplate={`%s \\ ${siteTitle}`}
|
||||||
|
meta={[
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
content: description,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
property: 'og:title',
|
||||||
|
content: title ? `${title} \\\\ ${siteTitle}` : siteTitle,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
property: 'og:url',
|
||||||
|
content: 'https://looseantenna.fm',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
property: 'og:description',
|
||||||
|
content: description,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
property: 'og:image',
|
||||||
|
content: imgSrc,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
property: 'og:type',
|
||||||
|
content: 'website',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'twitter:card',
|
||||||
|
content: 'summary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'twitter:title',
|
||||||
|
content: title,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'twitter:image',
|
||||||
|
content: `${imgSrc}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'twitter:description',
|
||||||
|
content: description,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'apple-mobile-web-app-capable',
|
||||||
|
content: 'yes',
|
||||||
|
},
|
||||||
|
(noindex ? {
|
||||||
|
name: 'robots',
|
||||||
|
content: 'noindex',
|
||||||
|
} : {}),
|
||||||
|
].concat()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SEO.defaultProps = {
|
||||||
|
meta: [],
|
||||||
|
description: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
SEO.propTypes = {
|
||||||
|
description: string,
|
||||||
|
metaImg: string,
|
||||||
|
title: string.isRequired,
|
||||||
|
noindex: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SEO
|
@ -1,44 +1,29 @@
|
|||||||
import { Fragment, h } from 'preact'
|
import { Fragment, h } from 'preact'
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
|
||||||
|
|
||||||
import { string } from 'prop-types'
|
import { string } from 'prop-types'
|
||||||
|
|
||||||
import { VideoWrapper, Iframe, StyledCrossSvg as CrossSvg } from './styles'
|
import { Iframe } from './styles'
|
||||||
|
|
||||||
const Video = ({ url, onClose }) => {
|
const VideoEmbed = ({ url, ...rest }) => {
|
||||||
const overlayTimeout = useRef(null)
|
console.log({ url })
|
||||||
const [overlayActive, setOverlayActiveState] = useState(false)
|
const id = url.split('/').pop()
|
||||||
|
const src = `https://tv.undersco.re/videos/embed/${id}?title=0&warningTitle=0&peertubeLink=0`
|
||||||
const activateOverlay = () => {
|
|
||||||
clearTimeout(overlayTimeout.current)
|
|
||||||
overlayTimeout.current = null
|
|
||||||
setOverlayActiveState(true)
|
|
||||||
|
|
||||||
overlayTimeout.current = setTimeout(
|
|
||||||
() => setOverlayActiveState(false),
|
|
||||||
1500
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<VideoWrapper $active={overlayActive}>
|
|
||||||
<CrossSvg $active={overlayActive} onClick={onClose} size="64" />
|
|
||||||
</VideoWrapper>
|
|
||||||
<Iframe
|
<Iframe
|
||||||
onMouseMove={activateOverlay}
|
|
||||||
width="560"
|
width="560"
|
||||||
height="315"
|
height="315"
|
||||||
src={url}
|
src={src}
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
||||||
allowfullscreen
|
allowfullscreen
|
||||||
|
sandbox="allow-same-origin allow-scripts allow-popups"
|
||||||
|
{...rest}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Video.propTypes = {
|
VideoEmbed.propTypes = {
|
||||||
url: string,
|
url: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Video
|
export default VideoEmbed
|
||||||
|
@ -1,43 +1,6 @@
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { colours } from '../../assets/theme'
|
|
||||||
import CrossSvg from '../Svg/Cross'
|
|
||||||
|
|
||||||
export const VideoWrapper = styled.div`
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
z-index: 2;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
/* background-color: ${props => (props.$active ? 'red' : 'green')}; */
|
|
||||||
`
|
|
||||||
export const Iframe = styled.iframe`
|
export const Iframe = styled.iframe`
|
||||||
z-index: 1;
|
width: 100%;
|
||||||
width: 100vw;
|
height: 100%;
|
||||||
height: 100vh;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const StyledCrossSvg = styled(CrossSvg)`
|
|
||||||
pointer-events: all;
|
|
||||||
position: fixed;
|
|
||||||
right: 2.5em;
|
|
||||||
top: 2em;
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
cursor: pointer;
|
|
||||||
stroke: ${colours.midnightDarker};
|
|
||||||
z-index: 12;
|
|
||||||
opacity: ${props => (props.$active ? '1' : '0.2')};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
@ -16,4 +16,15 @@ export const slugify = (title) => {
|
|||||||
|
|
||||||
return str
|
return str
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const capitaliseFirstLetter = word =>
|
||||||
|
word ? `${word?.charAt(0).toUpperCase()}${word?.slice(1)}` : '';
|
||||||
|
|
||||||
|
export const camelise = str => {
|
||||||
|
return str
|
||||||
|
.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
|
||||||
|
return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
|
||||||
|
})
|
||||||
|
.replace(/\s+/g, '');
|
||||||
|
};
|
53
src/layouts/InfoLayout/index.js
Normal file
53
src/layouts/InfoLayout/index.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { h } from 'preact'
|
||||||
|
import { bool } from 'prop-types'
|
||||||
|
|
||||||
|
import { H1, Label } from '../../components/Text'
|
||||||
|
import {
|
||||||
|
Wrapper,
|
||||||
|
TaglineContainer,
|
||||||
|
Content,
|
||||||
|
Hero,
|
||||||
|
FadeBottom,
|
||||||
|
} from './styles'
|
||||||
|
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
import { NdcLogo, RFLogo } from '../../components/Logo'
|
||||||
|
|
||||||
|
const InfoLayout = ({ title, subtitle, image, children, theme }) => {
|
||||||
|
console.log({ theme })
|
||||||
|
return (
|
||||||
|
<Wrapper theme={theme}>
|
||||||
|
<Content>
|
||||||
|
{children}
|
||||||
|
</Content>
|
||||||
|
<Hero image={image}>
|
||||||
|
<div>
|
||||||
|
<H1>{title}</H1>
|
||||||
|
<H1
|
||||||
|
css={`
|
||||||
|
max-width: 50%;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{subtitle}
|
||||||
|
</H1>
|
||||||
|
</div>
|
||||||
|
<TaglineContainer>
|
||||||
|
<a href="https://newdesigncongress.org/">
|
||||||
|
<NdcLogo active colour={colours.offwhite} />
|
||||||
|
</a>
|
||||||
|
<Label size="24">{'//'}</Label>
|
||||||
|
<a href="https://reclaimfutures.org/">
|
||||||
|
<RFLogo active colour={colours.offwhite} />
|
||||||
|
</a>
|
||||||
|
</TaglineContainer>
|
||||||
|
<FadeBottom />
|
||||||
|
</Hero>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoLayout.propTypes = {
|
||||||
|
loading: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfoLayout
|
@ -1,7 +1,7 @@
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { colours } from '../../assets/theme'
|
import { colours } from '../../assets/theme'
|
||||||
|
|
||||||
import Logo from '../Logo'
|
import Logo from '../../components/Logo'
|
||||||
|
|
||||||
const heroWidth = 'calc(100vw - 600px - 4em)'
|
const heroWidth = 'calc(100vw - 600px - 4em)'
|
||||||
|
|
||||||
@ -10,23 +10,41 @@ export const Wrapper = styled.div`
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: ${colours.midnight};
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
p,
|
|
||||||
h1,
|
|
||||||
h2 {
|
|
||||||
color: ${colours.rose};
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 1200px) {
|
@media screen and (max-width: 1200px) {
|
||||||
padding: 1.5em;
|
padding: 1.5em;
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 800px) {
|
@media screen and (max-width: 800px) {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
background-color: ${props => props.theme.background};
|
||||||
|
p,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
color: ${props => props.theme.foreground};
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: ${props => props.theme.highlight};
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: ${props => props.theme.foreground};
|
||||||
|
border-color: ${props => props.theme.foreground};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: ${props => props.theme.background};
|
||||||
|
color: ${props => props.theme.background};
|
||||||
|
background-color: ${props => props.theme.foreground};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
export const Top = styled.div`
|
export const Top = styled.div`
|
35
src/layouts/Page/index.js
Normal file
35
src/layouts/Page/index.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Fragment, h } from 'preact'
|
||||||
|
import PropTypes, { oneOfType, shape, string } from 'prop-types'
|
||||||
|
|
||||||
|
import SEO from '../../components/Seo'
|
||||||
|
|
||||||
|
// import Header from '../../molecules/Header'
|
||||||
|
import { capitaliseFirstLetter } from '../../helpers/string'
|
||||||
|
|
||||||
|
const Page = ({ children, title = '', description, metaImg, backTo, noindex }) => (
|
||||||
|
<Fragment>
|
||||||
|
<SEO
|
||||||
|
title={title.toLowerCase() === 'index' ? title : capitaliseFirstLetter(title)}
|
||||||
|
description={description}
|
||||||
|
metaImg={metaImg}
|
||||||
|
noindex={noindex}
|
||||||
|
/>
|
||||||
|
{/* <Header /> */}
|
||||||
|
{children}
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
|
||||||
|
Page.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
title: oneOfType([
|
||||||
|
string,
|
||||||
|
shape({
|
||||||
|
en: string,
|
||||||
|
fr: string,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
description: string,
|
||||||
|
metaImg: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page
|
@ -1,42 +1,25 @@
|
|||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
import { h, Fragment } from 'preact'
|
import { h, Fragment } from 'preact'
|
||||||
import { useState, useEffect } from 'preact/hooks'
|
|
||||||
import { isFuture, isPast } from 'date-fns'
|
|
||||||
import striptags from 'striptags'
|
import striptags from 'striptags'
|
||||||
|
|
||||||
import { H1 } from '../../components/Text'
|
import { H1 } from '../../components/Text'
|
||||||
import Markdown from '../../components/Markdown'
|
import Markdown from '../../components/Markdown'
|
||||||
import translations from '../../data/strings'
|
import translations from '../../data/strings'
|
||||||
import InfoLayout from '../../components/InfoLayout'
|
import InfoLayout from '../../layouts/InfoLayout'
|
||||||
import VideoEmbed from '../../components/VideoEmbed'
|
import VideoEmbed from '../../components/VideoEmbed'
|
||||||
import {
|
import {
|
||||||
EpisodeCard,
|
EpisodeCard,
|
||||||
Title,
|
Title,
|
||||||
InfoContent,
|
InfoContent,
|
||||||
PositionedCross as CrossSvg,
|
|
||||||
Row,
|
Row,
|
||||||
ActionButton as Button,
|
ActionButton as Button,
|
||||||
Trailer,
|
TrailerContainer,
|
||||||
} from './styles'
|
} from './styles'
|
||||||
|
|
||||||
import intro from '../../data/intro.md'
|
|
||||||
// import credits from '../../data/credits.md'
|
|
||||||
|
|
||||||
import config from '../../data/config'
|
import config from '../../data/config'
|
||||||
import trailerThumb from '../../assets/img/main_thumb.png'
|
import Page from '../../layouts/Page'
|
||||||
|
|
||||||
const SeriesPage = ({ data }) => {
|
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({ past: data.episodes.past, future: data.episodes.future })
|
|
||||||
|
|
||||||
const credits = `
|
const credits = `
|
||||||
## Credits
|
## Credits
|
||||||
@ -57,77 +40,71 @@ const SeriesPage = ({ data }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InfoLayout title={data.title} subtitle={striptags(data.subtitle)} image={data.image}>
|
<Page title={data.title}>
|
||||||
{embedURL ? (
|
<InfoLayout title={data.title} subtitle={data.subtitle} image={data.image} theme={data.theme}>
|
||||||
<VideoEmbed onClose={deactivateEmbed} url={embedURL} />
|
<Fragment>
|
||||||
) : null}
|
<InfoContent>
|
||||||
<Fragment>
|
<H1>{data.title}:</H1>
|
||||||
<InfoContent>
|
<H1>{data.subtitle}</H1>
|
||||||
<H1>{data.title}:</H1>
|
<Markdown withLinebreaks>{data.description}</Markdown>
|
||||||
<H1>{data.subtitle}</H1>
|
|
||||||
<Markdown withLinebreaks>{data.description}</Markdown>
|
|
||||||
|
|
||||||
<Trailer imgSrc={trailerThumb} onClick={onClickTrailerButton} />
|
{data.trailer ? (
|
||||||
|
<TrailerContainer>
|
||||||
|
<VideoEmbed url={data.trailer} />
|
||||||
|
</TrailerContainer>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Row>
|
{data.links.length ?
|
||||||
<a
|
<Row wrap>
|
||||||
href="https://discord.gg/Xu9D3qVana"
|
{data.links.map(link => (
|
||||||
target="_blank"
|
<a
|
||||||
rel="noopener noreferrer"
|
href={link.resourceUrl}
|
||||||
>
|
target="_blank"
|
||||||
<Button>Join the Discord</Button>
|
rel="noopener noreferrer"
|
||||||
</a>
|
>
|
||||||
<a
|
<Button>{link.summary}</Button>
|
||||||
href="https://ndc.substack.com/subscribe"
|
</a>
|
||||||
target="_blank"
|
))}
|
||||||
rel="noopener noreferrer"
|
</Row> : null
|
||||||
>
|
}
|
||||||
<Button>Subscribe to the mailing list</Button>
|
</InfoContent>
|
||||||
</a>
|
{data.episodes.future.length ? (
|
||||||
</Row>
|
<Fragment>
|
||||||
</InfoContent>
|
<Title>{translations.en.nextStream}:</Title>
|
||||||
{/* {currentVideo && (
|
{data.episodes.future.map(feeditem => (
|
||||||
<Fragment>
|
<EpisodeCard
|
||||||
<Title>{translations.en.nowPlaying}:</Title>
|
theme={data.theme}
|
||||||
<EpisodeCard {...currentVideo} />
|
key={feeditem.start}
|
||||||
</Fragment>
|
tzShort={tzShort}
|
||||||
)}
|
{...feeditem}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
) : null}
|
||||||
|
{data.episodes.past.length ? (
|
||||||
|
<Fragment>
|
||||||
|
<Title>Past streams:</Title>
|
||||||
|
{data.episodes.past.map(feeditem => (
|
||||||
|
<EpisodeCard
|
||||||
|
theme={data.theme}
|
||||||
|
key={feeditem.beginsOn}
|
||||||
|
hasPassed
|
||||||
|
onClickButton={() =>
|
||||||
|
setEmbedUrl(`${config.peertube_root}${feeditem.embedPath}`)
|
||||||
|
}
|
||||||
|
{...feeditem}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
) : null}
|
||||||
|
<InfoContent>
|
||||||
|
<Title>Credits</Title>
|
||||||
|
<Markdown>{credits}</Markdown>
|
||||||
|
</InfoContent>
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
*/}
|
</InfoLayout>
|
||||||
{data.episodes.future.length ? (
|
</Page>
|
||||||
<Fragment>
|
|
||||||
<Title>{translations.en.nextStream}:</Title>
|
|
||||||
{data.episodes.future.map(feeditem => (
|
|
||||||
<EpisodeCard
|
|
||||||
key={feeditem.start}
|
|
||||||
tzShort={tzShort}
|
|
||||||
{...feeditem}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Fragment>
|
|
||||||
) : null}
|
|
||||||
{data.episodes.past.length ? (
|
|
||||||
<Fragment>
|
|
||||||
<Title>Past streams:</Title>
|
|
||||||
{data.episodes.past.map(feeditem => (
|
|
||||||
<EpisodeCard
|
|
||||||
key={feeditem.beginsOn}
|
|
||||||
hasPassed
|
|
||||||
onClickButton={() =>
|
|
||||||
setEmbedUrl(`${config.peertube_root}${feeditem.embedPath}`)
|
|
||||||
}
|
|
||||||
{...feeditem}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Fragment>
|
|
||||||
) : null}
|
|
||||||
<InfoContent>
|
|
||||||
<Title>Credits</Title>
|
|
||||||
<Markdown>{credits}</Markdown>
|
|
||||||
</InfoContent>
|
|
||||||
</Fragment>
|
|
||||||
|
|
||||||
</InfoLayout>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,14 +16,8 @@ import { Link } from '../../components/Link'
|
|||||||
import Button from '../../components/Button'
|
import Button from '../../components/Button'
|
||||||
|
|
||||||
export const TrailerContainer = styled.div`
|
export const TrailerContainer = styled.div`
|
||||||
background: url(${props => props.imgSrc});
|
height: 22em;
|
||||||
height: 20em;
|
|
||||||
background-size: cover;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border: 1px solid ${colours.midnightDarker};
|
border: 1px solid ${colours.midnightDarker};
|
||||||
cursor: pointer;
|
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
@ -35,7 +29,6 @@ export const TrailerContainer = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
color: ${colours.midnightDarker};
|
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
@ -45,14 +38,6 @@ export const TrailerContainer = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
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)`
|
export const ActionButton = styled(Button)`
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@ -62,6 +47,7 @@ export const Row = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
flex-wrap: ${props => props.wrap ? 'wrap' : 'nowrap'};
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: block;
|
display: block;
|
||||||
@ -135,7 +121,7 @@ const VCImg = styled.img`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const ItemTitleWrapper = styled.div`
|
const ItemTitleWrapper = styled.div`
|
||||||
margin-bottom: 0.3em;
|
margin-bottom: 1em;
|
||||||
`
|
`
|
||||||
|
|
||||||
const DateLabel = styled(Label)`
|
const DateLabel = styled(Label)`
|
||||||
@ -159,19 +145,19 @@ export const EpisodeCard = ({
|
|||||||
videoUrl,
|
videoUrl,
|
||||||
onClickButton,
|
onClickButton,
|
||||||
tzShort,
|
tzShort,
|
||||||
image
|
image,
|
||||||
|
theme,
|
||||||
}) => {
|
}) => {
|
||||||
const startDate = new Date(beginsOn)
|
const startDate = new Date(beginsOn)
|
||||||
console.log({ startDate })
|
|
||||||
const utcDate = zonedTimeToUtc(startDate, 'Europe/Berlin')
|
const utcDate = zonedTimeToUtc(startDate, 'Europe/Berlin')
|
||||||
|
|
||||||
const { timeZone } = Intl.DateTimeFormat().resolvedOptions()
|
const { timeZone } = Intl.DateTimeFormat().resolvedOptions()
|
||||||
const zonedDate = utcToZonedTime(utcDate, timeZone)
|
const zonedDate = utcToZonedTime(utcDate, timeZone)
|
||||||
return (
|
return (
|
||||||
<VCWrapper>
|
<VCWrapper>
|
||||||
<DateLabel size={textSizes.lg}>
|
<DateLabel size={textSizes.lg} colour={theme.foreground}>
|
||||||
{`${hasPassed ? translations.en.streamDatePast : ''}`}
|
{`${hasPassed ? translations.en.streamDatePast : ''}`}
|
||||||
<Span bold colour={colours.rose}>
|
<Span bold colour={theme.foreground}>
|
||||||
{hasPassed
|
{hasPassed
|
||||||
? format(zonedDate, 'dd/MM/yy')
|
? format(zonedDate, 'dd/MM/yy')
|
||||||
: `${format(zonedDate, 'do LLLL y // HH:mm')} ${tzShort}`}
|
: `${format(zonedDate, 'do LLLL y // HH:mm')} ${tzShort}`}
|
||||||
|
@ -15,9 +15,12 @@ export const getPostByKey = (posts, key) => {
|
|||||||
return filteredPostItems.length ? filteredPostItems[0].body : null
|
return filteredPostItems.length ? filteredPostItems[0].body : null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getResourcesByKey = (resources, key) => {
|
export const getResourcesByKey = (resources, key, lookup = 'title') => {
|
||||||
const filteredResources = resources.elements.length ? resources.elements.filter(resource => resource.title === key) : []
|
const filteredResources = resources.elements.length ? resources.elements.filter(resource => resource[lookup] === key) : []
|
||||||
return filteredResources.length ? filteredResources[0].resourceUrl : null
|
|
||||||
|
if (!filteredResources.length) return null
|
||||||
|
|
||||||
|
return filteredResources
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPeertubeIDfromUrl = (string) => string && string.includes('https://tv.undersco.re') ? string.split('/').pop() : string
|
export const getPeertubeIDfromUrl = (string) => string && string.includes('https://tv.undersco.re') ? string.split('/').pop() : string
|
@ -3,6 +3,7 @@ import striptags from 'striptags'
|
|||||||
import { isFuture, isPast } from 'date-fns'
|
import { isFuture, isPast } from 'date-fns'
|
||||||
import { slugify } from '../helpers/string'
|
import { slugify } from '../helpers/string'
|
||||||
import { getMetadataByKey, getPostByKey, getResourcesByKey, getPeertubeIDfromUrl } from './helpers'
|
import { getMetadataByKey, getPostByKey, getResourcesByKey, getPeertubeIDfromUrl } from './helpers'
|
||||||
|
import { colours, defaultTheme } from '../assets/theme'
|
||||||
|
|
||||||
export const [useSeriesStore] = create(set => ({
|
export const [useSeriesStore] = create(set => ({
|
||||||
series: [],
|
series: [],
|
||||||
@ -20,6 +21,10 @@ export const [useSeriesStore] = create(set => ({
|
|||||||
peertubeId: getPeertubeIDfromUrl(getMetadataByKey(ep, 'mz:live:peertube:url')),
|
peertubeId: getPeertubeIDfromUrl(getMetadataByKey(ep, 'mz:live:peertube:url')),
|
||||||
})) : []
|
})) : []
|
||||||
|
|
||||||
|
const trailer = getResourcesByKey(resources, 'SERIES_TRAILER')?.[0]?.resourceUrl ?? null
|
||||||
|
const theme = striptags(getPostByKey(posts, 'THEME'))
|
||||||
|
|
||||||
|
|
||||||
const series = {
|
const series = {
|
||||||
title: name,
|
title: name,
|
||||||
subtitle: striptags(summary),
|
subtitle: striptags(summary),
|
||||||
@ -33,7 +38,9 @@ export const [useSeriesStore] = create(set => ({
|
|||||||
},
|
},
|
||||||
slug: slugify(name),
|
slug: slugify(name),
|
||||||
credits: getPostByKey(posts, 'SERIES_CREDITS'),
|
credits: getPostByKey(posts, 'SERIES_CREDITS'),
|
||||||
trailerId: getPeertubeIDfromUrl(getResourcesByKey(resources, 'SERIES_TRAILER'))
|
trailer,
|
||||||
|
links: getResourcesByKey(resources, 'LINK') || [],
|
||||||
|
theme: theme ? JSON.parse(theme) : defaultTheme
|
||||||
}
|
}
|
||||||
return series
|
return series
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user