bunch of styling

This commit is contained in:
Benjamin Jones 2021-03-24 16:24:34 +01:00
parent d4a07710ba
commit aa7ffc900b
19 changed files with 434 additions and 201 deletions

90
app.js
View File

@ -1,26 +1,21 @@
import { h } from 'preact'
import { useState, useEffect } from 'preact/hooks'
import 'regenerator-runtime/runtime'
import axios from 'axios'
import { isWithinInterval, subHours } from 'date-fns'
import { addHours } from 'date-fns/esm'
import Video from './src/components/Video'
import config from './src/data/config'
import Info from './src/components/Info'
import { useCalendar } from './src/hooks/data'
import { useEventStream } from './src/hooks/data'
import { useTimeout } from './src/hooks/timerHooks'
// const appStates = [
// 'noStream',
// 'streamLive',
// 'streamFinished'
// ]
export default () => {
const [isPlaying, setIsPlaying] = useState(false)
const [videoUrl, setVideoUrl] = useState(null)
const [feedData, setFeedData] = useState([])
const [currentVideo, setCurrentVideo] = useState(null)
const [streamIsLive, setStreamLive] = useState(false)
const [infoActive, setInfoActive] = useState(false)
const [minLoadTimePassed, setMinTimeUp] = useState(false)
const { data, loading } = useCalendar()
const { data, loading } = useEventStream()
useTimeout(() => {
setMinTimeUp(true)
@ -28,40 +23,26 @@ export default () => {
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.forEach((stream, index) => {
// if (stream.title === 'A Wider Screen') {
if (index === 0) {
setCurrentVideo(stream)
}
if (
isWithinInterval(new Date(), {
start: subHours(stream.start, 1),
end: addHours(stream.end, 1),
})
) {
setCurrentVideo(stream)
}
if (
isWithinInterval(new Date(), {
start: stream.start,
end: stream.end,
})
) {
setStreamLive(`${config.peertube_root}${stream.embedPath}`)
}
})
}
@ -69,16 +50,23 @@ export default () => {
return (
<div>
{false ? (
{currentVideo && !infoActive && minLoadTimePassed ? (
<Video
playing={isPlaying}
setPlaying={setIsPlaying}
src={videoUrl}
title={config.next_stream.title}
org={config.next_stream.org}
src={`${config.peertube_root}${currentVideo.embedPath}`}
title={currentVideo.title}
org={currentVideo.channel.displayName}
setInfoActive={setInfoActive}
/>
) : (
<Info data={feedData} loading={loading || !minLoadTimePassed} />
<Info
data={data}
loading={loading || !minLoadTimePassed}
infoActive={infoActive}
currentVideo={currentVideo}
setInfoActive={setInfoActive}
/>
)}
</div>
)

BIN
src/assets/img/IconSM.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -3,7 +3,7 @@ export const colours = {
midnightDarker: '#112B39',
offwhite: '#f6f4f5',
white: '#fff',
white: '#ffffff',
highlight: '#03a59e',
roseDarker: '#FEB9B3',
rose: '#F1CFCD',

View File

@ -1,19 +1,40 @@
import { h } from 'preact'
import { useEffect, useRef, useState } from 'preact/hooks'
import { useWindowDimensions } from '../../hooks/dom'
import { useToggle } from '../../hooks/utility'
import { ChatWrapper } from './styles'
import { Label } from '../Text'
import { ChatFrame, ChatWrapper, ChatHeader, CloseBox } from './styles'
import { colours } from '../../assets/theme'
const Chat = ({}) => {
return (
const { width, height } = useWindowDimensions()
const [chatIsOpen, toggleChatOpen] = useToggle(true)
return chatIsOpen ? (
<ChatWrapper>
<iframe
src="https://titanembeds.com/embed/803918964082212905?css=215&defaultchannel=817134294199566356&lang=en_EN"
height="500"
width="350"
frameBorder="0"
title="discord-chat"
class="titanembed"
/>
<ChatFrame>
<ChatHeader chatIsOpen>
<Label weight="400" size={24}>
Chat
</Label>
<CloseBox colour={colours.white} size={18} onClick={toggleChatOpen} />
</ChatHeader>
<iframe
src="https://titanembeds.com/embed/803918964082212905?css=215&defaultchannel=817134294199566356&lang=en_EN"
height={(height / 4) * 3}
width="350"
frameBorder="0"
title="discord-chat"
className="titanembed"
/>
</ChatFrame>
</ChatWrapper>
) : (
<ChatHeader chatIsOpen={false} onClick={toggleChatOpen}>
<Label weight="400" size={24}>
Chat
</Label>
</ChatHeader>
)
}

View File

@ -1,10 +1,55 @@
import styled from 'styled-components'
import { colours, ui } from '../../assets/theme'
import CrossSvg from '../Svg/Cross'
import { Label } from '../Text'
export const ChatFrame = styled.div`
/* border: 2px solid ${colours.white}; */
/* padding: 20px; */
`
export const ChatWrapper = styled.div`
position: fixed;
z-index: 10;
bottom: 0;
right: 32px;
bottom: -5px;
right: 0;
backdrop-filter: blur(20px);
background-color: ${colours.midnight}40;
/* padding: 20px; */
border-radius: ${ui.borderRadius}px;
`
export const ChatHeader = styled.div`
position: absolute;
bottom: ${props => (props.chatIsOpen ? 'initial' : 0)};
top: ${props => (props.chatIsOpen ? '4px' : 'initial')};
/* cursor: ${props => (props.dragging ? 'grabbing' : 'grab')}; */
border-radius: ${ui.borderRadius}px 0 0 0;
height: 32px;
box-sizing: border-box;
display: flex;
align-items: center;
width: ${props => (props.chatIsOpen ? '100%' : 'fit-content')};
justify-content: space-between;
padding: 0px 0px 3px 0px;
right: 0;
box-sizing: content-box;
border: ${props =>
props.chatIsOpen ? 'none' : `1px solid ${colours.white}`};
border-bottom: ${props =>
props.chatIsOpen ? `1px solid ${colours.white}75` : 'none'};
border-right: none;
label {
margin-left: 12px;
margin-right: ${props => (props.chatIsOpen ? '0' : '12px')}
}
`
export const CloseBox = styled(CrossSvg)`
padding: 12px;
cursor: pointer;
`

View File

@ -1,39 +1,55 @@
/* eslint-disable react/prop-types */
import { h, Fragment } from 'preact'
import { useEffect, useRef, useState } from 'preact/hooks'
import { isBefore } from 'date-fns'
import { isFuture, isPast } from 'date-fns'
import { P } from '../Text'
import translations from '../../data/strings'
import InfoLayout from '../InfoLayout'
import { VideoCard, Title, InfoContent } from './styles'
import {
VideoCard,
Title,
InfoContent,
PositionedCross as CrossSvg,
} from './styles'
const Info = ({ data, loading }) => {
const now = new Date()
const Info = ({ data, loading, infoActive, setInfoActive, currentVideo }) => {
const pastStreams =
data && data.length
? data.filter(feeditem => isBefore(new Date(feeditem.end), now))
? data.filter(feeditem => isPast(new Date(feeditem.end)))
: []
const futureStreams =
data && data.length
? data.filter(feeditem => isBefore(now, new Date(feeditem.start)))
? data.filter(
feeditem =>
feeditem.id !== (currentVideo && currentVideo.id) &&
isFuture(new Date(feeditem.start))
)
: []
console.log({ currentVideo })
return (
<InfoLayout
title={
data && data.length
? `${translations.en.nextStream}:`
: translations.en.noStreams
}
loading={loading}
>
<InfoLayout loading={loading}>
{infoActive && <CrossSvg onClick={() => setInfoActive(false)} />}
{!loading && (
<InfoContent>
{futureStreams.map(feeditem => (
<VideoCard key={feeditem.start} {...feeditem} />
))}
{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} {...feeditem} />
))}
</Fragment>
)}
{pastStreams.length ? (
<Fragment>
<Title>{translations.en.pastStream}:</Title>

View File

@ -5,8 +5,11 @@ import { colours } from '../../assets/theme'
import config from '../../data/config'
import Logo from '../Logo'
import translations from '../../data/strings'
import CrossSvg from '../Svg/Cross'
import { P, H1, H2, Span, Label } from '../Text'
import Link from '../Link'
import { bool, instanceOf, string } from 'prop-types'
export const Wrapper = styled.div`
height: 100vh;
@ -51,10 +54,24 @@ export const Title = styled(H1)`
margin: 0.3em 0;
`
export const PositionedCross = styled(CrossSvg)`
position: fixed;
right: 2.5em;
top: 2em;
width: 24px;
height: 24px;
cursor: pointer;
stroke: ${colours.midnightDarker};
&:hover {
opacity: 0.5;
}
`
const VCWrapper = styled.div`
max-width: 600px;
margin-bottom: 3em;
border-left: 7px solid ${colours.midnightDarker};
margin: 0 0 6em 2px;
border-left: 5px solid ${colours.midnightDarker};
padding-left: 1em;
`
@ -71,29 +88,52 @@ const DateLabel = styled(Label)`
display: block;
`
const LinkBlock = styled(Link)`
display: block;
width: 100%;
`
export const VideoCard = ({
name,
title,
description,
start,
end,
previewPath,
hasPassed,
}) => {
return (
<VCWrapper>
<ItemTitle>{name}</ItemTitle>
<VCImg src={`${config.peertube_root}${previewPath}`} alt="" />
<DateLabel colour={colours.midnight} size="18">
{`${
hasPassed
? translations.en.streamDatePast
: translations.en.streamDateFuture
}`}
<Span bold colour={colours.midnight}>
{format(new Date(start), 'hh:mm dd/MM/yy')}
</Span>
</DateLabel>
<P>{description}</P>
</VCWrapper>
)
videoUrl,
}) => (
<VCWrapper>
{videoUrl && hasPassed ? (
<LinkBlock href={videoUrl}>
<ItemTitle>{title}</ItemTitle>
<VCImg src={`${config.peertube_root}${previewPath}`} alt="" />
</LinkBlock>
) : (
<Fragment>
<ItemTitle>{title}</ItemTitle>
<VCImg src={`${config.peertube_root}${previewPath}`} alt="" />
</Fragment>
)}
<DateLabel colour={colours.midnight} size="18">
{`${
hasPassed
? translations.en.streamDatePast
: translations.en.streamDateFuture
}`}
<Span bold colour={colours.midnight}>
{format(new Date(start), 'hh:mm dd/MM/yy')}
</Span>
</DateLabel>
<P>{description}</P>
</VCWrapper>
)
VideoCard.propTypes = {
title: string,
description: string,
start: instanceOf(Date),
end: instanceOf(Date),
previewPath: string,
hasPassed: bool,
videoUrl: string,
}

View File

@ -7,7 +7,7 @@ import {
Wrapper,
PositionedLogo as Logo,
TaglineContainer,
Title,
LoaderWrapper,
Content,
Fade,
} from './styles'
@ -16,7 +16,7 @@ import { colours } from '../../assets/theme'
import Loader from '../Loader'
import { useTimeout } from '../../hooks/timerHooks'
const InfoLayout = ({ title, children, loading }) => {
const InfoLayout = ({ children, loading }) => {
return (
<Wrapper>
<Fade>
@ -24,13 +24,12 @@ const InfoLayout = ({ title, children, loading }) => {
</Fade>
<Content>
{loading ? (
<div>
<LoaderWrapper>
<Loader />
</div>
</LoaderWrapper>
) : (
<Title>{title}</Title>
children
)}
{children}
</Content>
<TaglineContainer>
@ -46,7 +45,6 @@ const InfoLayout = ({ title, children, loading }) => {
}
InfoLayout.propTypes = {
title: string,
loading: bool,
}

View File

@ -59,8 +59,14 @@ export const Fade = styled.div`
left: 0;
background: ${getGradient('bottom')};
`
export const Title = styled(H1)`
margin: 0.5em 0;
export const LoaderWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
position: fixed;
top: 0;
width: 50vw;
`
export const Content = styled.div`

View File

@ -0,0 +1,6 @@
import styled from 'styled-components'
export const Link = styled.a`
text-decoration: none;
`
export default Link

View File

@ -0,0 +1,16 @@
import { h } from 'preact'
import { bool, string } from 'prop-types'
const Cross = ({ colour = 'inherit', size, ...rest }) => (
<svg viewBox="0 0 100 100" height={size} {...rest}>
<path
stroke={colour}
strokeLinecap="none"
strokeLinejoin="none"
strokeWidth="18"
d="M11.354 11.757l77.637 77.636m-77.627 0L89 11.757"
/>
</svg>
)
export default Cross

View File

@ -4,16 +4,18 @@ import { bool, func, string } from 'prop-types'
import 'regenerator-runtime/runtime'
import { PeerTubePlayer } from '@peertube/embed-api'
import Logo from '../Logo'
import Chat from '../Chat'
import { H2 } from '../Text'
import Overlay from '../VideoOverlay'
import { useToggle } from '../../hooks/utility'
import { VideoWrapper, Iframe } from './styles'
const Video = ({ playing, setPlaying, src, title, org }) => {
const Video = ({ playing, setPlaying, src, title, org, setInfoActive }) => {
const videoiFrame = useRef(null)
const overlayTimeout = useRef(null)
const videoWrapperEl = useRef(null)
const [videoReady, setVideoReady] = useState(false)
// const [isFullscreen, toggleFullscreen] = useToggle(false)
// const [chatActive, setChatActive] = useState(false)
const [overlayActive, setOverlayActiveState] = useState(true)
const ptVideo = useRef(null)
@ -43,6 +45,37 @@ const Video = ({ playing, setPlaying, src, title, org }) => {
}
}, [playing])
const goFullscreen = () => {
// toggleFullscreen()
}
const exitFullscreen = () => {
// toggleFullscreen()
}
const handleKeyPress = keyCode => {
if (keyCode === 32) {
setPlaying(!playing)
}
// if (keyCode === 70 && !isFullscreen) {
// console.log('goFullscreen')
// goFullscreen()
// }
// if (keyCode === 70 && isFullscreen) {
// console.log('exitFullscreen')
// exitFullscreen()
// }
}
useEffect(() => {
window.addEventListener('keydown', ({ keyCode }) => handleKeyPress(keyCode))
return () =>
window.removeEventListener('keydown', ({ keyCode }) =>
handleKeyPress(keyCode)
)
}, [])
const activateOverlay = () => {
clearTimeout(overlayTimeout.current)
setOverlayActiveState(true)
@ -58,8 +91,14 @@ const Video = ({ playing, setPlaying, src, title, org }) => {
$active={overlayActive || !playing}
onClick={() => setPlaying(!playing)}
onMouseMove={activateOverlay}
// ref={videoWrapperEl}
>
<Overlay active={overlayActive || !playing} title={title} org={org} />
<Overlay
active={overlayActive || !playing}
title={title}
org={org}
setInfoActive={setInfoActive}
/>
<Iframe
sandbox="allow-same-origin allow-scripts allow-popups"
src={`${src}?api=1&controls=false`}

View File

@ -1,14 +1,14 @@
import { h } from 'preact'
import { Fragment, h } from 'preact'
import { bool, string } from 'prop-types'
import Logo from '../Logo'
import { P } from '../Text'
import { OverlayWrapper, TopLeft } from './styles'
import { H2, P } from '../Text'
import { InfoButton, OverlayWrapper, TopLeft } from './styles'
const VideoOverlay = ({ active, title, org }) => {
const displayTitle = `${title}${org ? `${org}` : ''}`
const VideoOverlay = ({ active, title, org, setInfoActive }) => (
// const displayTitle = `${title}${org ? ` — ${org}` : ''}`
return (
<Fragment>
<OverlayWrapper>
<TopLeft $active={active}>
<Logo active={active} />
@ -18,12 +18,13 @@ const VideoOverlay = ({ active, title, org }) => {
margin-top: 16px;
`}
>
{displayTitle}
{title}
</P>
</TopLeft>
</OverlayWrapper>
)
}
<InfoButton $active={active} onClick={() => setInfoActive(true)} />
</Fragment>
)
VideoOverlay.propTypes = {
active: bool,

View File

@ -1,4 +1,6 @@
import styled, { css } from 'styled-components'
import { colours } from '../../assets/theme'
import burb from '../../assets/img/IconSM.png'
export const OverlayWrapper = styled.div`
z-index: 2;
@ -18,4 +20,39 @@ export const TopLeft = styled.div`
transform: translateY(0%);
opacity: 1;
`};
p,
svg {
backdrop-filter: blur(20px);
background-color: ${colours.midnight}40;
padding: 4px 8px;
display: inline-block;
margin-right: 45%;
border-radius: 5px;
}
`
export const InfoButton = styled.img.attrs(() => ({
src: burb,
}))`
opacity: 0.001;
transform: translateY(-20%);
transition: all 0.2s ease-in-out;
transition-delay: 0.2s;
position: fixed;
right: 2em;
top: 2em;
width: 45px;
height: 45px;
z-index: 100;
${props =>
props.$active &&
css`
transform: translateY(0%);
opacity: 1;
`};
&:hover {
filter: invert(1);
}
`

View File

@ -2,6 +2,7 @@ export default {
en: {
nextStream: 'Next streams',
pastStream: 'Latest streams',
nowPlaying: 'Now playing',
noStreams: 'No upcoming streams, check back soon.',
underscoreTagline: ['LEAVE THE', 'SURVEILLANCE ECONOMY', '— TOGETHER.'],
streamDateFuture: 'Going live at: ',

View File

@ -1,20 +1,63 @@
import { useEffect, useState } from 'preact/hooks'
import { useEffect, useState, useRef } from 'preact/hooks'
import axios from 'axios'
import ical from 'ical'
import config from '../data/config'
export const useCalendar = () => {
const [data, setData] = useState(null)
export const useEventStream = () => {
const [data, setData] = useState([])
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))
const calItems = Object.values(ical.parseICS(responseData))
.filter(feedItem => feedItem.type === 'VEVENT')
.sort((a, b) => new Date(a.start) - new Date(b.start))
setData(streamsData)
await Promise.all(
calItems.map(async calItem => {
if (calItem.url) {
const id = calItem.url.val.split('/').pop()
const {
data: {
account,
category,
channel,
embedPath,
language,
state,
previewPath,
views,
duration,
},
data: nesd,
} = await axios.get(`https://tv.undersco.re/api/v1/videos/${id}`)
console.log({ nesd })
const item = {
title: calItem.summary,
account,
category,
channel,
description: calItem.description,
embedPath,
language,
state,
previewPath,
views,
start: calItem.start,
end: calItem.end,
id,
duration,
videoUrl: calItem?.url?.val,
}
setData(arr => [...arr, item])
}
})
)
setLoading(false)
}
@ -24,73 +67,3 @@ export const useCalendar = () => {
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])

37
src/hooks/dom.js Normal file
View File

@ -0,0 +1,37 @@
import { useState, useEffect } from 'preact/hooks'
const getWidth = () =>
window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth
const getHeight = () =>
window.innerHeight ||
document.documentElement.clientHeight ||
document.body.clientHeight
// save current window width in the state object
export const useWindowDimensions = () => {
const [width, setWidth] = useState(getWidth())
const [height, setHeight] = useState(getHeight())
useEffect(() => {
let timeoutId = null
const resizeListener = () => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
setWidth(getWidth())
setHeight(getHeight())
}, 50)
}
window.addEventListener('resize', resizeListener)
// clean up function
return () => {
// remove resize listener
window.removeEventListener('resize', resizeListener)
}
}, [])
return { width, height }
}

9
src/hooks/utility.js Normal file
View File

@ -0,0 +1,9 @@
import { useCallback, useState } from 'preact/hooks'
export const useToggle = (initialValue = false) => {
const [value, setValue] = useState(initialValue)
const toggle = useCallback(() => {
setValue(v => !v)
}, [])
return [value, toggle]
}