diff --git a/app.js b/app.js index 3747590..b58a565 100644 --- a/app.js +++ b/app.js @@ -1,15 +1,16 @@ import { h } from 'preact' import { useState, useEffect } from 'preact/hooks' 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 config from './src/data/config' import Info from './src/components/Info' import { useEventCalendar, useEventApi } from './src/hooks/data' import { useTimeout } from './src/hooks/timerHooks' -export default () => { +export default (props) => { + + console.log({ props }) const [currentVideo, setCurrentVideo] = useState(null) const [streamIsLive, setStreamIsLive] = useState(false) const [infoActive, setInfoActive] = useState(false) @@ -60,7 +61,7 @@ export default () => { ) : ( { + 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 ? ( + + ) : ( + + + + {seriesData.length ? seriesData.map(series => ( + + + )) : null} + + + ) +} + const appEl = document.getElementById('app') diff --git a/package.json b/package.json index 386bb34..b77e8b9 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,14 @@ "axios": "^0.21.1", "date-fns": "^2.19.0", "date-fns-tz": "^1.1.4", + "dotenv": "^10.0.0", "ical": "^0.8.0", "ical.js": "^1.4.0", "markdown-to-jsx": "^7.1.2", "preact": "^10.5.12", "prop-types": "^15.7.2", + "react-router-dom": "^5.3.0", + "striptags": "^3.2.0", "styled-components": "^5.2.1" }, "devDependencies": { @@ -41,4 +44,4 @@ "sass": "^1.32.8", "scss": "^0.2.4" } -} \ No newline at end of file +} diff --git a/src/components/Info/index.js b/src/components/Info/index.js index d01683e..20e4aca 100644 --- a/src/components/Info/index.js +++ b/src/components/Info/index.js @@ -15,17 +15,16 @@ import { PositionedCross as CrossSvg, Row, ActionButton as Button, - Img, Trailer, } from './styles' import intro from '../../data/intro.md' import credits from '../../data/credits.md' -import { colours } from '../../assets/theme' + import config from '../../data/config' 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 [embedURL, setEmbedUrl] = useState('') @@ -36,26 +35,26 @@ const Info = ({ data, loading, infoActive, setInfoActive, currentVideo }) => { setEmbedUrl('') } - const pastStreams = - data && data.length - ? data.filter(feeditem => isPast(new Date(feeditem.end))) - : [] + // 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 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 = @@ -71,79 +70,75 @@ const Info = ({ data, loading, infoActive, setInfoActive, currentVideo }) => { } return ( - - {embedURL ? ( + + {/* {embedURL ? ( - ) : null} - {infoActive && ( - setInfoActive(false)} /> - )} - {!loading && ( - - -

The Para-Real:

-

Finding the Future in Unexpected Places

- {intro} + ) : null} */} + + +

The Para-Real:

+

Finding the Future in Unexpected Places

+ {intro} - + - - - - - - - - -
- {currentVideo && ( - - {translations.en.nowPlaying}: - - - )} + + + + + + + + +
+ {/* {currentVideo && ( + + {translations.en.nowPlaying}: + + + )} - {futureStreams.length ? ( - - {translations.en.nextStream}: - {futureStreams.map(feeditem => ( - - ))} - - ) : null} + {futureStreams.length ? ( + + {translations.en.nextStream}: + {futureStreams.map(feeditem => ( + + ))} + + ) : null} + + {pastStreams.length ? ( + + {translations.en.pastStream}: + {pastStreams.map(feeditem => ( + + setEmbedUrl(`${config.peertube_root}${feeditem.embedPath}`) + } + {...feeditem} + /> + ))} + + ) : null} */} + {/* + {credits} + */} +
- {pastStreams.length ? ( - - {translations.en.pastStream}: - {pastStreams.map(feeditem => ( - - setEmbedUrl(`${config.peertube_root}${feeditem.embedPath}`) - } - {...feeditem} - /> - ))} - - ) : null} - - {credits} - - - )}
) } diff --git a/src/components/InfoLayout/index.js b/src/components/InfoLayout/index.js index 45c935d..34e28ad 100644 --- a/src/components/InfoLayout/index.js +++ b/src/components/InfoLayout/index.js @@ -17,7 +17,7 @@ import Loader from '../Loader' import { useTimeout } from '../../hooks/timerHooks' import { NdcLogo, RFLogo } from '../Logo' -const InfoLayout = ({ children, loading }) => ( +const InfoLayout = ({ title, subtitle, children, loading }) => ( {loading ? ( @@ -30,15 +30,13 @@ const InfoLayout = ({ children, loading }) => (
-

The

-

Para-

-

Real

+

{title}

- Finding the Future in Unexpected Places + {subtitle}

diff --git a/src/components/InfoLayout/styles.js b/src/components/InfoLayout/styles.js index 73af966..4517666 100644 --- a/src/components/InfoLayout/styles.js +++ b/src/components/InfoLayout/styles.js @@ -39,10 +39,8 @@ export const Top = styled.div` 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 + `linear-gradient(to ${direction}, ${lightDark === 'dark' ? gradientColourDark : gradientColourLight + }ee 0%,${lightDark === 'dark' ? gradientColourDark : gradientColourLight }00 100%);` // prettier-ignore @@ -78,7 +76,7 @@ export const Hero = styled.div` } h1:not(:last-of-type) { - font-size: 30vh; + font-size: 15vw; @media screen and (max-height: 600px) { font-size: 20vh; diff --git a/src/components/Loader/index.js b/src/components/Loader/index.js index b8b7ed6..de865de 100644 --- a/src/components/Loader/index.js +++ b/src/components/Loader/index.js @@ -4,16 +4,18 @@ import { useInterval, useTimeout } from '../../hooks/timerHooks' import { colours } from '../../assets/theme' import { H1 } from '../Text' +const defaultLoader = [':..', '.:.', '..:', '...'] // const symbols = ['⌏', '⌎', '⌌', '⌍'] const Loader = ({ active = true, offset = 0, - animation = [':..', '.:.', '..:', '...'], + animation = defaultLoader, + colour = colours.rose, }) => { const [text, setText] = useState('.') const arrayPosition = useRef(offset) - const rate = 350 + const rate = 300 useInterval( () => { @@ -29,7 +31,7 @@ const Loader = ({ ) return ( -

+

{text}

) diff --git a/src/helpers/environment.js b/src/helpers/environment.js new file mode 100644 index 0000000..90e5f0c --- /dev/null +++ b/src/helpers/environment.js @@ -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, +} diff --git a/src/helpers/string.js b/src/helpers/string.js new file mode 100644 index 0000000..28e6794 --- /dev/null +++ b/src/helpers/string.js @@ -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 + +} \ No newline at end of file diff --git a/src/pages/LoaderLayout/index.js b/src/pages/LoaderLayout/index.js new file mode 100644 index 0000000..ae38ed8 --- /dev/null +++ b/src/pages/LoaderLayout/index.js @@ -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 = () => ( + + + + + + + + {translations && + translations.en.underscoreTagline.map(line => ( +

{line}

+ ))} +
+
+) + +export default LoaderLayout diff --git a/src/pages/LoaderLayout/styles.js b/src/pages/LoaderLayout/styles.js new file mode 100644 index 0000000..44ac7a6 --- /dev/null +++ b/src/pages/LoaderLayout/styles.js @@ -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; + } + } +` diff --git a/src/pages/SeriesPage/index.js b/src/pages/SeriesPage/index.js new file mode 100644 index 0000000..d371217 --- /dev/null +++ b/src/pages/SeriesPage/index.js @@ -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 ( + + {embedURL ? ( + + ) : null} + + +

{data.name}:

+

{data.summary}

+ {intro} + + + + + + + + + + + +
+ {/* {currentVideo && ( + + {translations.en.nowPlaying}: + + + )} + + {futureStreams.length ? ( + + {translations.en.nextStream}: + {futureStreams.map(feeditem => ( + + ))} + + ) : null} + + {pastStreams.length ? ( + + {translations.en.pastStream}: + {pastStreams.map(feeditem => ( + + setEmbedUrl(`${config.peertube_root}${feeditem.embedPath}`) + } + {...feeditem} + /> + ))} + + ) : null} */} + {/* + {credits} + */} +
+ +
+ ) +} + +export default SeriesPage diff --git a/src/pages/SeriesPage/styles.js b/src/pages/SeriesPage/styles.js new file mode 100644 index 0000000..c132cbd --- /dev/null +++ b/src/pages/SeriesPage/styles.js @@ -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 => ( + +
+ + +
+
+) + +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 =>

{title}

) + +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 ( + + + {`${hasPassed ? translations.en.streamDatePast : ''}`} + + {hasPassed + ? format(zonedDate, 'dd/MM/yy') + : `${format(zonedDate, 'do LLLL y // HH:mm')} ${tzShort}`} + + + {videoUrl && hasPassed ? ( + + {renderTitles(title)} + + + ) : ( + + {renderTitles(title)} + + + )} +

{description}

+ {hasPassed ? ( + + ) : ( + + + + )} +
+ ) +} + +VideoCard.propTypes = { + title: string, + description: string, + start: instanceOf(Date), + previewPath: string, + hasPassed: bool, + videoUrl: string, +}