diff --git a/app.js b/app.js
index 6f1169f..10f95af 100644
--- a/app.js
+++ b/app.js
@@ -1,14 +1,13 @@
import { h } from 'preact'
-import axios from 'axios'
-// eslint-disable-next-line import/no-extraneous-dependencies
+import { useState, useEffect } from 'preact/hooks'
import 'regenerator-runtime/runtime'
-import { useEffect, useState } from 'preact/hooks'
+import axios from 'axios'
import Video from './src/components/Video'
import config from './src/data/config'
import Info from './src/components/Info'
-import { useFetch } from './src/assets/hooks/calendar'
-import { P } from './src/components/Text'
+import { useCalendar } from './src/hooks/data'
+import { useTimeout } from './src/hooks/timerHooks'
// const appStates = [
// 'noStream',
@@ -19,13 +18,58 @@ import { P } from './src/components/Text'
export default () => {
const [isPlaying, setIsPlaying] = useState(false)
const [videoUrl, setVideoUrl] = useState(null)
- // const [feedData, setFeedData] = useState([])
+ const [feedData, setFeedData] = useState([])
+ const [minLoadTimePassed, setMinTimeUp] = useState(false)
+ const { data, loading } = useCalendar()
- const { data: feedData, loading } = useFetch(`${config.calendar}`)
+ useTimeout(() => {
+ setMinTimeUp(true)
+ }, 1500)
+
+ useEffect(() => {
+ if (data && data.length) {
+ data.forEach(async (calItem, index) => {
+ if (calItem.url) {
+ const id = calItem.url.val.split('/').pop()
+
+ const {
+ data: {
+ account,
+ category,
+ channel,
+ embedPath,
+ language,
+ name,
+ state,
+ previewPath,
+ views,
+ },
+ } = await axios.get(`https://tv.undersco.re/api/v1/videos/${id}`)
+
+ const item = {
+ name,
+ account,
+ category,
+ channel,
+ description: calItem.description,
+ embedPath,
+ language,
+ state,
+ previewPath,
+ views,
+ start: calItem.start,
+ end: calItem.end,
+ id,
+ }
+ setFeedData(arr => [...arr, item])
+ }
+ })
+ }
+ }, [data])
return (
- {/* {false ? (
+ {false ? (
)
}
diff --git a/package.json b/package.json
index cca5985..9dd4d37 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,8 @@
"dependencies": {
"@peertube/embed-api": "^0.0.4",
"axios": "^0.21.1",
+ "date-fns": "^2.19.0",
+ "ical": "^0.8.0",
"preact": "^10.5.12",
"prop-types": "^15.7.2",
"styled-components": "^5.2.1"
diff --git a/src/assets/fonts/Karla/Karla-Regular.ttf b/src/assets/fonts/Karla/Karla-Regular.ttf
new file mode 100644
index 0000000..a08cc9b
Binary files /dev/null and b/src/assets/fonts/Karla/Karla-Regular.ttf differ
diff --git a/src/assets/hooks/calendar.js b/src/assets/hooks/calendar.js
deleted file mode 100644
index b2c88b8..0000000
--- a/src/assets/hooks/calendar.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { useEffect, useState } from 'preact/hooks'
-import axios from 'axios'
-
-export const useFetch = url => {
- const [data, setData] = useState(null)
- const [loading, setLoading] = useState(true)
-
- async function fetchData() {
- setLoading(true)
-
- const { data: responseData } = await axios.get(url)
- console.log('url', url)
- console.log('responseData', responseData)
- setData(responseData)
- setLoading(false)
- }
-
- useEffect(() => {
- fetchData()
- }, [])
-
- return { loading, data }
-}
diff --git a/src/assets/js/index.js b/src/assets/js/index.js
deleted file mode 100644
index 7bc7711..0000000
--- a/src/assets/js/index.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { PeerTubePlayer } from '@peertube/embed-api';
-import 'regenerator-runtime/runtime';
-import { toggleVideoPlaying } from './video-controls';
-import { getVideoData } from './scheduling';
-
-const streamData = getVideoData();
-
-const videoWrapper = document.getElementById('video-wrapper');
-const overlay = document.getElementById('overlay');
-const videoiFrame = document.querySelector('iframe.video-player');
-
-const video = {
- isPlaying: false,
- overlayVisible: false,
-};
-
-export const setUpPlayer = async () => {
- const player = new PeerTubePlayer(videoiFrame);
-
- await player.ready;
-
- onPlayerReady(player);
- app();
-};
-
-const app = () => {
- if (streamData && streamData) {
- console.log({ streamData });
- }
-};
-
-const onPlayerReady = (player) => {
- videoWrapper.addEventListener('mousemove', showOverlay);
-
- videoWrapper.addEventListener('click', () => {
- toggleVideoPlaying({
- player,
- video,
- onPlay: () => {
- hideOverlay();
- },
- onPause: () => {
- showOverlay();
- },
- });
- video.isPlaying = !video.isPlaying;
- });
-};
-
-const showOverlay = () => {
- if (!video.overlayVisible) {
- videoWrapper.classList.add('active');
- overlay.classList.add('active');
-
- setTimeout(hideOverlay, 2000);
- }
- video.overlayVisible = true;
-};
-
-const hideOverlay = () => {
- if (video.isPlaying) {
- video.overlayVisible = false;
- videoWrapper.classList.remove('active');
- overlay.classList.remove('active');
- }
-};
-
-setUpPlayer();
diff --git a/src/assets/js/scheduling.js b/src/assets/js/scheduling.js
deleted file mode 100644
index 44a8fa2..0000000
--- a/src/assets/js/scheduling.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import axios from 'axios';
-import 'regenerator-runtime/runtime';
-import config from '../../data/conf.json';
-
-export const getVideoData = async () => {
- console.log({ config });
- const { data } = await axios.get(`https://tv.undersco.re/api/v1/videos/${config.next_stream.peertube_id}`);
-
- console.log(data);
-
- return data;
-};
diff --git a/src/assets/js/video-controls.js b/src/assets/js/video-controls.js
deleted file mode 100644
index a02933e..0000000
--- a/src/assets/js/video-controls.js
+++ /dev/null
@@ -1,15 +0,0 @@
-export const toggleVideoPlaying = ({ player, video, onPlay, onPause }) => {
- console.log('video', video);
-
- if (video.isPlaying) {
- player.pause();
- if (typeof onPause === 'function') {
- onPause();
- }
- } else {
- player.play();
- if (typeof onPlay === 'function') {
- onPlay();
- }
- }
-};
diff --git a/src/assets/styles/fontface.scss b/src/assets/styles/fontface.scss
index 8f5fde1..836a2b0 100644
--- a/src/assets/styles/fontface.scss
+++ b/src/assets/styles/fontface.scss
@@ -1,3 +1,11 @@
+/* Karla Regular */
+@font-face {
+ font-family: 'Karla';
+ src: url('../fonts/Karla/Karla-Regular.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+}
+
/* Karla Medium */
@font-face {
font-family: 'Karla';
diff --git a/src/components/Info/helpers.js b/src/components/Info/helpers.js
new file mode 100644
index 0000000..4b9b9e6
--- /dev/null
+++ b/src/components/Info/helpers.js
@@ -0,0 +1,4 @@
+export const sortData = data =>
+ Object.values(data)
+ .filter(feedItem => feedItem.type === 'VEVENT')
+ .sort((a, b) => new Date(a.start) - new Date(b.start))
diff --git a/src/components/Info/index.js b/src/components/Info/index.js
index 46d1ad1..60a06f2 100644
--- a/src/components/Info/index.js
+++ b/src/components/Info/index.js
@@ -1,42 +1,51 @@
-import { h } from 'preact'
+/* eslint-disable react/prop-types */
+import { h, Fragment } from 'preact'
import { useEffect, useRef, useState } from 'preact/hooks'
+import { isBefore } from 'date-fns'
-import { H1, H2, P } from '../Text'
-import {
- Wrapper,
- PositionedLogo as Logo,
- TaglineContainer,
- Top,
-} from './styles'
+import { P } from '../Text'
import translations from '../../data/strings'
import InfoLayout from '../InfoLayout'
+import { VideoCard, Title, InfoContent } from './styles'
-const allowedChannels = [7, 4] // ReclaimFutures, NDC
+const Info = ({ data, loading }) => {
+ const now = new Date()
+ const pastStreams =
+ data && data.length
+ ? data.filter(feeditem => isBefore(new Date(feeditem.end), now))
+ : []
-const Info = ({ data }) => {
- // const [feed, setFeed] = useState([])
-
- // useEffect(() => {
- // setFeed(sortData(data))
- // }, [data])
-
- useEffect(() => {
- console.log({ data })
- }, [data])
+ const futureStreams =
+ data && data.length
+ ? data.filter(feeditem => isBefore(now, new Date(feeditem.start)))
+ : []
return (
-
- ding dong
+
+ {!loading && (
+
+ {futureStreams.map(feeditem => (
+
+ ))}
+ {pastStreams.length ? (
+
+ {translations.en.pastStream}:
+ {pastStreams.map(feeditem => (
+
+ ))}
+
+ ) : null}
+
+ )}
)
}
-const sortData = data => {
- // if (!data || data?.length === 0) return
- // console.log('data', data)
- // return data.filter(feedItem => {
- // return allowedChannels.includes(feedItem.channel.id) && feedItem.isLive
- // })
-}
-
export default Info
diff --git a/src/components/Info/styles.js b/src/components/Info/styles.js
index 0b403ea..ebcef2a 100644
--- a/src/components/Info/styles.js
+++ b/src/components/Info/styles.js
@@ -1,6 +1,12 @@
+import { format } from 'date-fns'
+import { h, Fragment } from 'preact'
import styled from 'styled-components'
import { colours } from '../../assets/theme'
+import config from '../../data/config'
import Logo from '../Logo'
+import translations from '../../data/strings'
+
+import { P, H1, H2, Span, Label } from '../Text'
export const Wrapper = styled.div`
height: 100vh;
@@ -27,6 +33,10 @@ export const Wrapper = styled.div`
export const Top = styled.div``
+export const InfoContent = styled.div`
+ padding-bottom: 1em;
+`
+
export const PositionedLogo = styled(Logo)`
margin-bottom: 64px;
`
@@ -36,3 +46,54 @@ export const TaglineContainer = styled.div`
margin-top: 32px;
}
`
+
+export const Title = styled(H1)`
+ margin: 0.3em 0;
+`
+
+const VCWrapper = styled.div`
+ max-width: 600px;
+ margin-bottom: 3em;
+ border-left: 7px solid ${colours.midnightDarker};
+ padding-left: 1em;
+`
+
+const VCImg = styled.img`
+ width: 100%;
+`
+
+const ItemTitle = styled(H2)`
+ margin-bottom: 0.3em;
+`
+
+const DateLabel = styled(Label)`
+ margin: 1em 0;
+ display: block;
+`
+
+export const VideoCard = ({
+ name,
+ description,
+ start,
+ end,
+ previewPath,
+ hasPassed,
+}) => {
+ return (
+
+ {name}
+
+
+ {`${
+ hasPassed
+ ? translations.en.streamDatePast
+ : translations.en.streamDateFuture
+ }`}
+
+ {format(new Date(start), 'hh:mm dd/MM/yy')}
+
+
+ {description}
+
+ )
+}
diff --git a/src/components/InfoLayout/index.js b/src/components/InfoLayout/index.js
index ab3ec95..12ccc79 100644
--- a/src/components/InfoLayout/index.js
+++ b/src/components/InfoLayout/index.js
@@ -1,34 +1,53 @@
import { h } from 'preact'
import { useEffect, useRef, useState } from 'preact/hooks'
+import { bool, string } from 'prop-types'
-import { H1, H2, P } from '../Text'
+import { H1, H2 } from '../Text'
import {
Wrapper,
PositionedLogo as Logo,
TaglineContainer,
- Top,
+ Title,
Content,
+ Fade,
} from './styles'
import translations from '../../data/strings'
import { colours } from '../../assets/theme'
+import Loader from '../Loader'
+import { useTimeout } from '../../hooks/timerHooks'
-const InfoLayout = ({ title, children }) => {
+const InfoLayout = ({ title, children, loading }) => {
return (
-
+
+
+
- {title}
+ {loading ? (
+
+
+
+ ) : (
+ {title}
+ )}
{children}
{translations &&
translations.en.underscoreTagline.map(line => (
- {line}
+
+ {line}
+
))}
)
}
+InfoLayout.propTypes = {
+ title: string,
+ loading: bool,
+}
+
export default InfoLayout
diff --git a/src/components/InfoLayout/styles.js b/src/components/InfoLayout/styles.js
index e2a385a..01b9afa 100644
--- a/src/components/InfoLayout/styles.js
+++ b/src/components/InfoLayout/styles.js
@@ -1,5 +1,8 @@
import styled from 'styled-components'
import { colours } from '../../assets/theme'
+
+import { H1 } from '../Text'
+
import Logo from '../Logo'
export const Wrapper = styled.div`
@@ -41,14 +44,52 @@ export const Wrapper = styled.div`
export const Top = styled.div`
width: 50%;
`
-export const Content = styled.div``
-export const PositionedLogo = styled(Logo)`
+const gradientColour = '#F8E5E2'
+const getGradient = direction =>
+ `linear-gradient(to ${direction}, ${gradientColour}ee 0%,${gradientColour}00 100%);`
+
+// prettier-ignore
+export const Fade = styled.div`
+ width: 100%;
+ background-color: linear;
position: fixed;
- top: 2em;
+ padding: 2em 0 1em 2em;
+ top: 0;
+ left: 0;
+ background: ${getGradient('bottom')};
`
+export const Title = styled(H1)`
+ margin: 0.5em 0;
+`
+
+export const Content = styled.div`
+ /* margin-bottom: 3em; */
+`
+
+export const PositionedLogo = styled(Logo)``
export const TaglineContainer = styled.div`
+ background: ${getGradient('top')};
position: fixed;
- bottom: 2em;
+ bottom: 0em;
+ padding-bottom: 0.5em;
+ right: 1em;
+ pointer-events: none;
+
+ @media screen and (max-width: 1200px) {
+ width: 100vw;
+ right: auto;
+ left: 1.5em;
+ h1 {
+ font-size: 32px;
+ text-align: left;
+ }
+ }
+
+ @media screen and (max-width: 800px) {
+ h1 {
+ font-size: 24px;
+ }
+ }
`
diff --git a/src/components/Loader/index.js b/src/components/Loader/index.js
new file mode 100644
index 0000000..b8b7ed6
--- /dev/null
+++ b/src/components/Loader/index.js
@@ -0,0 +1,38 @@
+import { h } from 'preact'
+import { useRef, useState } from 'preact/hooks'
+import { useInterval, useTimeout } from '../../hooks/timerHooks'
+import { colours } from '../../assets/theme'
+import { H1 } from '../Text'
+
+// const symbols = ['⌏', '⌎', '⌌', '⌍']
+
+const Loader = ({
+ active = true,
+ offset = 0,
+ animation = [':..', '.:.', '..:', '...'],
+}) => {
+ const [text, setText] = useState('.')
+ const arrayPosition = useRef(offset)
+ const rate = 350
+
+ useInterval(
+ () => {
+ setText(animation[arrayPosition.current])
+
+ if (arrayPosition.current === animation.length - 1) {
+ arrayPosition.current = 0
+ } else {
+ arrayPosition.current += 1
+ }
+ },
+ active ? rate : null
+ )
+
+ return (
+
+ {text}
+
+ )
+}
+
+export default Loader
diff --git a/src/assets/js/chat.js b/src/components/Loader/styles.js
similarity index 100%
rename from src/assets/js/chat.js
rename to src/components/Loader/styles.js
diff --git a/src/components/Text/index.js b/src/components/Text/index.js
index 1d97be1..5f89fb3 100644
--- a/src/components/Text/index.js
+++ b/src/components/Text/index.js
@@ -25,7 +25,7 @@ const Text = ({
weight={weight}
lineHeight={lineHeight}
$fontFamily={fontFamily}
- size={size}
+ $size={size}
selectable={selectable}
{...rest}
>
@@ -66,7 +66,7 @@ export const H1 = ({ children, ...rest }) => {
(
(
export const P = ({ children, ...rest }) => (
@@ -102,15 +102,15 @@ export const P = ({ children, ...rest }) => (
)
export const Span = ({ children, ...rest }) => (
-
+
{children}
)
-export const Label = ({ children, ...rest }) => (
+export const Label = ({ children, size, ...rest }) => (
{
+ const [data, setData] = useState(null)
+ const [loading, setLoading] = useState(true)
+
+ async function fetchData() {
+ setLoading(true)
+
+ const { data: responseData } = await axios.get(`${config.calendar}`)
+ const streamsData = Object.values(ical.parseICS(responseData))
+ .filter(feedItem => feedItem.type === 'VEVENT')
+ .sort((a, b) => new Date(a.start) - new Date(b.start))
+ setData(streamsData)
+ setLoading(false)
+ }
+
+ useEffect(() => {
+ fetchData()
+ }, [])
+
+ return { loading, data }
+}
+
+// export const useCalendar = () => {
+// const [data, setData] = useState(null)
+// const [loading, setLoading] = useState(true)
+
+// async function fetchData() {
+// setLoading(true)
+
+// const { data: responseData } = await axios.get(`${config.calendar}`)
+// const streamsData = Object.values(ical.parseICS(responseData))
+// .filter(feedItem => feedItem.type === 'VEVENT')
+// .sort((a, b) => new Date(a.start) - new Date(b.start))
+// setData(streamsData)
+// setLoading(false)
+// }
+
+// useEffect(() => {
+// fetchData()
+// }, [])
+
+// return { loading, data }
+// }
+// useEffect(() => {
+// const feedPromise =
+// data &&
+// data.map(async feedItem => {
+// if (feedItem.url) {
+// const id = feedItem.url.val.split('/').pop()
+
+// const {
+// data: {
+// account,
+// category,
+// channel,
+// description,
+// embedPath,
+// language,
+// name,
+// state,
+// previewPath,
+// views,
+// },
+// } = await axios.get(`https://tv.undersco.re/api/v1/videos/${id}`)
+
+// const item = {
+// name,
+// account,
+// category,
+// channel,
+// description,
+// embedPath,
+// language,
+// state,
+// previewPath,
+// views,
+// start: feedItem.start,
+// end: feedItem.end,
+// }
+
+// console.log(item)
+
+// return item
+// }
+// return null
+// })
+
+// feedPromise.then(result => {
+// console.log(result)
+// })
+// }, [data])
diff --git a/src/hooks/timerHooks.js b/src/hooks/timerHooks.js
new file mode 100644
index 0000000..4383a31
--- /dev/null
+++ b/src/hooks/timerHooks.js
@@ -0,0 +1,38 @@
+/* eslint-disable consistent-return */
+import { useEffect, useRef } from 'preact/hooks'
+
+export const useInterval = (callback, interval) => {
+ const savedCallback = useRef()
+
+ useEffect(() => {
+ savedCallback.current = callback
+ }, [callback])
+
+ useEffect(() => {
+ function tick() {
+ savedCallback.current()
+ }
+ if (interval !== null) {
+ const id = setInterval(tick, interval)
+ return () => clearInterval(id)
+ }
+ }, [interval])
+}
+
+export const useTimeout = (callback, timeout) => {
+ const savedCallback = useRef()
+
+ useEffect(() => {
+ savedCallback.current = callback
+ }, [callback])
+
+ useEffect(() => {
+ function tick() {
+ savedCallback.current()
+ }
+ if (timeout !== null) {
+ const id = setTimeout(tick, timeout)
+ return () => clearTimeout(id)
+ }
+ }, [timeout])
+}
diff --git a/yarn.lock b/yarn.lock
index e7fc05b..358eca8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2483,6 +2483,11 @@ data-urls@^1.1.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
+date-fns@^2.19.0:
+ version "2.19.0"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.19.0.tgz#65193348635a28d5d916c43ec7ce6fbd145059e1"
+ integrity sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg==
+
deasync@^0.1.14:
version "0.1.21"
resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.21.tgz#bb11eabd4466c0d8776f0d82deb8a6126460d30f"
@@ -3612,6 +3617,13 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
+ical@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/ical/-/ical-0.8.0.tgz#aa93f021dfead58e54aaa22076a11ca07d65886b"
+ integrity sha512-/viUSb/RGLLnlgm0lWRlPBtVeQguQRErSPYl3ugnUaKUnzQswKqOG3M8/P1v1AB5NJwlHTuvTq1cs4mpeG2rCg==
+ dependencies:
+ rrule "2.4.1"
+
iconv-lite@0.4.24, iconv-lite@^0.4.17:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -4263,6 +4275,11 @@ lru-cache@^4.0.1, lru-cache@^4.1.5:
pseudomap "^1.0.2"
yallist "^2.1.2"
+luxon@^1.3.3:
+ version "1.26.0"
+ resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.26.0.tgz#d3692361fda51473948252061d0f8561df02b578"
+ integrity sha512-+V5QIQ5f6CDXQpWNICELwjwuHdqeJM1UenlZWx5ujcRMc9venvluCjFb4t5NYLhb6IhkbMVOxzVuOqkgMxee2A==
+
magic-string@^0.22.4:
version "0.22.5"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e"
@@ -5792,6 +5809,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
+rrule@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/rrule/-/rrule-2.4.1.tgz#1d0db4e45f2b0e92e2cca62d2f7093729ac7ec94"
+ integrity sha512-+NcvhETefswZq13T8nkuEnnQ6YgUeZaqMqVbp+ZiFDPCbp3AVgQIwUvNVDdMNrP05bKZG9ddDULFp0qZZYDrxg==
+ optionalDependencies:
+ luxon "^1.3.3"
+
run-async@^2.2.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"