bunch of styling
This commit is contained in:
parent
d4a07710ba
commit
aa7ffc900b
90
app.js
90
app.js
@ -1,26 +1,21 @@
|
|||||||
import { h } from 'preact'
|
import { h } from 'preact'
|
||||||
import { useState, useEffect } from 'preact/hooks'
|
import { useState, useEffect } from 'preact/hooks'
|
||||||
import 'regenerator-runtime/runtime'
|
import { isWithinInterval, subHours } from 'date-fns'
|
||||||
import axios from 'axios'
|
import { addHours } from 'date-fns/esm'
|
||||||
|
|
||||||
import Video from './src/components/Video'
|
import Video from './src/components/Video'
|
||||||
import config from './src/data/config'
|
import config from './src/data/config'
|
||||||
import Info from './src/components/Info'
|
import Info from './src/components/Info'
|
||||||
import { useCalendar } from './src/hooks/data'
|
import { useEventStream } from './src/hooks/data'
|
||||||
import { useTimeout } from './src/hooks/timerHooks'
|
import { useTimeout } from './src/hooks/timerHooks'
|
||||||
|
|
||||||
// const appStates = [
|
|
||||||
// 'noStream',
|
|
||||||
// 'streamLive',
|
|
||||||
// 'streamFinished'
|
|
||||||
// ]
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [isPlaying, setIsPlaying] = useState(false)
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
const [videoUrl, setVideoUrl] = useState(null)
|
const [currentVideo, setCurrentVideo] = useState(null)
|
||||||
const [feedData, setFeedData] = useState([])
|
const [streamIsLive, setStreamLive] = useState(false)
|
||||||
|
const [infoActive, setInfoActive] = useState(false)
|
||||||
const [minLoadTimePassed, setMinTimeUp] = useState(false)
|
const [minLoadTimePassed, setMinTimeUp] = useState(false)
|
||||||
const { data, loading } = useCalendar()
|
const { data, loading } = useEventStream()
|
||||||
|
|
||||||
useTimeout(() => {
|
useTimeout(() => {
|
||||||
setMinTimeUp(true)
|
setMinTimeUp(true)
|
||||||
@ -28,40 +23,26 @@ export default () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data && data.length) {
|
if (data && data.length) {
|
||||||
data.forEach(async (calItem, index) => {
|
data.forEach((stream, index) => {
|
||||||
if (calItem.url) {
|
// if (stream.title === 'A Wider Screen') {
|
||||||
const id = calItem.url.val.split('/').pop()
|
if (index === 0) {
|
||||||
|
setCurrentVideo(stream)
|
||||||
const {
|
}
|
||||||
data: {
|
if (
|
||||||
account,
|
isWithinInterval(new Date(), {
|
||||||
category,
|
start: subHours(stream.start, 1),
|
||||||
channel,
|
end: addHours(stream.end, 1),
|
||||||
embedPath,
|
})
|
||||||
language,
|
) {
|
||||||
name,
|
setCurrentVideo(stream)
|
||||||
state,
|
}
|
||||||
previewPath,
|
if (
|
||||||
views,
|
isWithinInterval(new Date(), {
|
||||||
},
|
start: stream.start,
|
||||||
} = await axios.get(`https://tv.undersco.re/api/v1/videos/${id}`)
|
end: stream.end,
|
||||||
|
})
|
||||||
const item = {
|
) {
|
||||||
name,
|
setStreamLive(`${config.peertube_root}${stream.embedPath}`)
|
||||||
account,
|
|
||||||
category,
|
|
||||||
channel,
|
|
||||||
description: calItem.description,
|
|
||||||
embedPath,
|
|
||||||
language,
|
|
||||||
state,
|
|
||||||
previewPath,
|
|
||||||
views,
|
|
||||||
start: calItem.start,
|
|
||||||
end: calItem.end,
|
|
||||||
id,
|
|
||||||
}
|
|
||||||
setFeedData(arr => [...arr, item])
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -69,16 +50,23 @@ export default () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{false ? (
|
{currentVideo && !infoActive && minLoadTimePassed ? (
|
||||||
<Video
|
<Video
|
||||||
playing={isPlaying}
|
playing={isPlaying}
|
||||||
setPlaying={setIsPlaying}
|
setPlaying={setIsPlaying}
|
||||||
src={videoUrl}
|
src={`${config.peertube_root}${currentVideo.embedPath}`}
|
||||||
title={config.next_stream.title}
|
title={currentVideo.title}
|
||||||
org={config.next_stream.org}
|
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>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -35,4 +35,4 @@
|
|||||||
"sass": "^1.32.8",
|
"sass": "^1.32.8",
|
||||||
"scss": "^0.2.4"
|
"scss": "^0.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
BIN
src/assets/img/IconSM.png
Normal file
BIN
src/assets/img/IconSM.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
@ -3,7 +3,7 @@ export const colours = {
|
|||||||
midnightDarker: '#112B39',
|
midnightDarker: '#112B39',
|
||||||
offwhite: '#f6f4f5',
|
offwhite: '#f6f4f5',
|
||||||
|
|
||||||
white: '#fff',
|
white: '#ffffff',
|
||||||
highlight: '#03a59e',
|
highlight: '#03a59e',
|
||||||
roseDarker: '#FEB9B3',
|
roseDarker: '#FEB9B3',
|
||||||
rose: '#F1CFCD',
|
rose: '#F1CFCD',
|
||||||
|
@ -1,19 +1,40 @@
|
|||||||
import { h } from 'preact'
|
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 = ({}) => {
|
const Chat = ({}) => {
|
||||||
return (
|
const { width, height } = useWindowDimensions()
|
||||||
|
const [chatIsOpen, toggleChatOpen] = useToggle(true)
|
||||||
|
return chatIsOpen ? (
|
||||||
<ChatWrapper>
|
<ChatWrapper>
|
||||||
<iframe
|
<ChatFrame>
|
||||||
src="https://titanembeds.com/embed/803918964082212905?css=215&defaultchannel=817134294199566356&lang=en_EN"
|
<ChatHeader chatIsOpen>
|
||||||
height="500"
|
<Label weight="400" size={24}>
|
||||||
width="350"
|
Chat
|
||||||
frameBorder="0"
|
</Label>
|
||||||
title="discord-chat"
|
<CloseBox colour={colours.white} size={18} onClick={toggleChatOpen} />
|
||||||
class="titanembed"
|
</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>
|
</ChatWrapper>
|
||||||
|
) : (
|
||||||
|
<ChatHeader chatIsOpen={false} onClick={toggleChatOpen}>
|
||||||
|
<Label weight="400" size={24}>
|
||||||
|
Chat
|
||||||
|
</Label>
|
||||||
|
</ChatHeader>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,55 @@
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { colours, ui } from '../../assets/theme'
|
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`
|
export const ChatWrapper = styled.div`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
bottom: 0;
|
bottom: -5px;
|
||||||
right: 32px;
|
right: 0;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
background-color: ${colours.midnight}40;
|
||||||
|
/* padding: 20px; */
|
||||||
border-radius: ${ui.borderRadius}px;
|
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;
|
||||||
|
`
|
||||||
|
@ -1,39 +1,55 @@
|
|||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
import { h, Fragment } from 'preact'
|
import { h, Fragment } from 'preact'
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
import { isFuture, isPast } from 'date-fns'
|
||||||
import { isBefore } from 'date-fns'
|
|
||||||
|
|
||||||
import { P } from '../Text'
|
import { P } from '../Text'
|
||||||
import translations from '../../data/strings'
|
import translations from '../../data/strings'
|
||||||
import InfoLayout from '../InfoLayout'
|
import InfoLayout from '../InfoLayout'
|
||||||
import { VideoCard, Title, InfoContent } from './styles'
|
import {
|
||||||
|
VideoCard,
|
||||||
|
Title,
|
||||||
|
InfoContent,
|
||||||
|
PositionedCross as CrossSvg,
|
||||||
|
} from './styles'
|
||||||
|
|
||||||
const Info = ({ data, loading }) => {
|
const Info = ({ data, loading, infoActive, setInfoActive, currentVideo }) => {
|
||||||
const now = new Date()
|
|
||||||
const pastStreams =
|
const pastStreams =
|
||||||
data && data.length
|
data && data.length
|
||||||
? data.filter(feeditem => isBefore(new Date(feeditem.end), now))
|
? data.filter(feeditem => isPast(new Date(feeditem.end)))
|
||||||
: []
|
: []
|
||||||
|
|
||||||
const futureStreams =
|
const futureStreams =
|
||||||
data && data.length
|
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 (
|
return (
|
||||||
<InfoLayout
|
<InfoLayout loading={loading}>
|
||||||
title={
|
{infoActive && <CrossSvg onClick={() => setInfoActive(false)} />}
|
||||||
data && data.length
|
|
||||||
? `${translations.en.nextStream}:`
|
|
||||||
: translations.en.noStreams
|
|
||||||
}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<InfoContent>
|
<InfoContent>
|
||||||
{futureStreams.map(feeditem => (
|
{currentVideo && (
|
||||||
<VideoCard key={feeditem.start} {...feeditem} />
|
<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 ? (
|
{pastStreams.length ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Title>{translations.en.pastStream}:</Title>
|
<Title>{translations.en.pastStream}:</Title>
|
||||||
|
@ -5,8 +5,11 @@ import { colours } from '../../assets/theme'
|
|||||||
import config from '../../data/config'
|
import config from '../../data/config'
|
||||||
import Logo from '../Logo'
|
import Logo from '../Logo'
|
||||||
import translations from '../../data/strings'
|
import translations from '../../data/strings'
|
||||||
|
import CrossSvg from '../Svg/Cross'
|
||||||
|
|
||||||
import { P, H1, H2, Span, Label } from '../Text'
|
import { P, H1, H2, Span, Label } from '../Text'
|
||||||
|
import Link from '../Link'
|
||||||
|
import { bool, instanceOf, string } from 'prop-types'
|
||||||
|
|
||||||
export const Wrapper = styled.div`
|
export const Wrapper = styled.div`
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@ -51,10 +54,24 @@ export const Title = styled(H1)`
|
|||||||
margin: 0.3em 0;
|
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`
|
const VCWrapper = styled.div`
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin-bottom: 3em;
|
margin: 0 0 6em 2px;
|
||||||
border-left: 7px solid ${colours.midnightDarker};
|
border-left: 5px solid ${colours.midnightDarker};
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -71,29 +88,52 @@ const DateLabel = styled(Label)`
|
|||||||
display: block;
|
display: block;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const LinkBlock = styled(Link)`
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
export const VideoCard = ({
|
export const VideoCard = ({
|
||||||
name,
|
title,
|
||||||
description,
|
description,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
previewPath,
|
previewPath,
|
||||||
hasPassed,
|
hasPassed,
|
||||||
}) => {
|
videoUrl,
|
||||||
return (
|
}) => (
|
||||||
<VCWrapper>
|
<VCWrapper>
|
||||||
<ItemTitle>{name}</ItemTitle>
|
{videoUrl && hasPassed ? (
|
||||||
<VCImg src={`${config.peertube_root}${previewPath}`} alt="" />
|
<LinkBlock href={videoUrl}>
|
||||||
<DateLabel colour={colours.midnight} size="18">
|
<ItemTitle>{title}</ItemTitle>
|
||||||
{`${
|
<VCImg src={`${config.peertube_root}${previewPath}`} alt="" />
|
||||||
hasPassed
|
</LinkBlock>
|
||||||
? translations.en.streamDatePast
|
) : (
|
||||||
: translations.en.streamDateFuture
|
<Fragment>
|
||||||
}`}
|
<ItemTitle>{title}</ItemTitle>
|
||||||
<Span bold colour={colours.midnight}>
|
<VCImg src={`${config.peertube_root}${previewPath}`} alt="" />
|
||||||
{format(new Date(start), 'hh:mm dd/MM/yy')}
|
</Fragment>
|
||||||
</Span>
|
)}
|
||||||
</DateLabel>
|
<DateLabel colour={colours.midnight} size="18">
|
||||||
<P>{description}</P>
|
{`${
|
||||||
</VCWrapper>
|
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,
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
Wrapper,
|
Wrapper,
|
||||||
PositionedLogo as Logo,
|
PositionedLogo as Logo,
|
||||||
TaglineContainer,
|
TaglineContainer,
|
||||||
Title,
|
LoaderWrapper,
|
||||||
Content,
|
Content,
|
||||||
Fade,
|
Fade,
|
||||||
} from './styles'
|
} from './styles'
|
||||||
@ -16,7 +16,7 @@ import { colours } from '../../assets/theme'
|
|||||||
import Loader from '../Loader'
|
import Loader from '../Loader'
|
||||||
import { useTimeout } from '../../hooks/timerHooks'
|
import { useTimeout } from '../../hooks/timerHooks'
|
||||||
|
|
||||||
const InfoLayout = ({ title, children, loading }) => {
|
const InfoLayout = ({ children, loading }) => {
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Fade>
|
<Fade>
|
||||||
@ -24,13 +24,12 @@ const InfoLayout = ({ title, children, loading }) => {
|
|||||||
</Fade>
|
</Fade>
|
||||||
<Content>
|
<Content>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div>
|
<LoaderWrapper>
|
||||||
<Loader />
|
<Loader />
|
||||||
</div>
|
</LoaderWrapper>
|
||||||
) : (
|
) : (
|
||||||
<Title>{title}</Title>
|
children
|
||||||
)}
|
)}
|
||||||
{children}
|
|
||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
<TaglineContainer>
|
<TaglineContainer>
|
||||||
@ -46,7 +45,6 @@ const InfoLayout = ({ title, children, loading }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
InfoLayout.propTypes = {
|
InfoLayout.propTypes = {
|
||||||
title: string,
|
|
||||||
loading: bool,
|
loading: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +59,14 @@ export const Fade = styled.div`
|
|||||||
left: 0;
|
left: 0;
|
||||||
background: ${getGradient('bottom')};
|
background: ${getGradient('bottom')};
|
||||||
`
|
`
|
||||||
export const Title = styled(H1)`
|
export const LoaderWrapper = styled.div`
|
||||||
margin: 0.5em 0;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 50vw;
|
||||||
`
|
`
|
||||||
|
|
||||||
export const Content = styled.div`
|
export const Content = styled.div`
|
||||||
|
6
src/components/Link/index.js
Normal file
6
src/components/Link/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
export const Link = styled.a`
|
||||||
|
text-decoration: none;
|
||||||
|
`
|
||||||
|
export default Link
|
16
src/components/Svg/Cross.js
Normal file
16
src/components/Svg/Cross.js
Normal 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
|
@ -4,16 +4,18 @@ import { bool, func, string } from 'prop-types'
|
|||||||
import 'regenerator-runtime/runtime'
|
import 'regenerator-runtime/runtime'
|
||||||
import { PeerTubePlayer } from '@peertube/embed-api'
|
import { PeerTubePlayer } from '@peertube/embed-api'
|
||||||
|
|
||||||
import Logo from '../Logo'
|
|
||||||
import Chat from '../Chat'
|
import Chat from '../Chat'
|
||||||
import { H2 } from '../Text'
|
|
||||||
import Overlay from '../VideoOverlay'
|
import Overlay from '../VideoOverlay'
|
||||||
|
import { useToggle } from '../../hooks/utility'
|
||||||
import { VideoWrapper, Iframe } from './styles'
|
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 videoiFrame = useRef(null)
|
||||||
const overlayTimeout = useRef(null)
|
const overlayTimeout = useRef(null)
|
||||||
|
const videoWrapperEl = useRef(null)
|
||||||
const [videoReady, setVideoReady] = useState(false)
|
const [videoReady, setVideoReady] = useState(false)
|
||||||
|
// const [isFullscreen, toggleFullscreen] = useToggle(false)
|
||||||
|
// const [chatActive, setChatActive] = useState(false)
|
||||||
const [overlayActive, setOverlayActiveState] = useState(true)
|
const [overlayActive, setOverlayActiveState] = useState(true)
|
||||||
const ptVideo = useRef(null)
|
const ptVideo = useRef(null)
|
||||||
|
|
||||||
@ -43,6 +45,37 @@ const Video = ({ playing, setPlaying, src, title, org }) => {
|
|||||||
}
|
}
|
||||||
}, [playing])
|
}, [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 = () => {
|
const activateOverlay = () => {
|
||||||
clearTimeout(overlayTimeout.current)
|
clearTimeout(overlayTimeout.current)
|
||||||
setOverlayActiveState(true)
|
setOverlayActiveState(true)
|
||||||
@ -58,8 +91,14 @@ const Video = ({ playing, setPlaying, src, title, org }) => {
|
|||||||
$active={overlayActive || !playing}
|
$active={overlayActive || !playing}
|
||||||
onClick={() => setPlaying(!playing)}
|
onClick={() => setPlaying(!playing)}
|
||||||
onMouseMove={activateOverlay}
|
onMouseMove={activateOverlay}
|
||||||
|
// ref={videoWrapperEl}
|
||||||
>
|
>
|
||||||
<Overlay active={overlayActive || !playing} title={title} org={org} />
|
<Overlay
|
||||||
|
active={overlayActive || !playing}
|
||||||
|
title={title}
|
||||||
|
org={org}
|
||||||
|
setInfoActive={setInfoActive}
|
||||||
|
/>
|
||||||
<Iframe
|
<Iframe
|
||||||
sandbox="allow-same-origin allow-scripts allow-popups"
|
sandbox="allow-same-origin allow-scripts allow-popups"
|
||||||
src={`${src}?api=1&controls=false`}
|
src={`${src}?api=1&controls=false`}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { h } from 'preact'
|
import { Fragment, h } from 'preact'
|
||||||
import { bool, string } from 'prop-types'
|
import { bool, string } from 'prop-types'
|
||||||
|
|
||||||
import Logo from '../Logo'
|
import Logo from '../Logo'
|
||||||
import { P } from '../Text'
|
import { H2, P } from '../Text'
|
||||||
import { OverlayWrapper, TopLeft } from './styles'
|
import { InfoButton, OverlayWrapper, TopLeft } from './styles'
|
||||||
|
|
||||||
const VideoOverlay = ({ active, title, org }) => {
|
const VideoOverlay = ({ active, title, org, setInfoActive }) => (
|
||||||
const displayTitle = `${title}${org ? ` — ${org}` : ''}`
|
// const displayTitle = `${title}${org ? ` — ${org}` : ''}`
|
||||||
|
|
||||||
return (
|
<Fragment>
|
||||||
<OverlayWrapper>
|
<OverlayWrapper>
|
||||||
<TopLeft $active={active}>
|
<TopLeft $active={active}>
|
||||||
<Logo active={active} />
|
<Logo active={active} />
|
||||||
@ -18,12 +18,13 @@ const VideoOverlay = ({ active, title, org }) => {
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{displayTitle}
|
{title}
|
||||||
</P>
|
</P>
|
||||||
</TopLeft>
|
</TopLeft>
|
||||||
</OverlayWrapper>
|
</OverlayWrapper>
|
||||||
)
|
<InfoButton $active={active} onClick={() => setInfoActive(true)} />
|
||||||
}
|
</Fragment>
|
||||||
|
)
|
||||||
|
|
||||||
VideoOverlay.propTypes = {
|
VideoOverlay.propTypes = {
|
||||||
active: bool,
|
active: bool,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import styled, { css } from 'styled-components'
|
import styled, { css } from 'styled-components'
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
import burb from '../../assets/img/IconSM.png'
|
||||||
|
|
||||||
export const OverlayWrapper = styled.div`
|
export const OverlayWrapper = styled.div`
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@ -18,4 +20,39 @@ export const TopLeft = styled.div`
|
|||||||
transform: translateY(0%);
|
transform: translateY(0%);
|
||||||
opacity: 1;
|
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);
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
@ -2,6 +2,7 @@ export default {
|
|||||||
en: {
|
en: {
|
||||||
nextStream: 'Next streams',
|
nextStream: 'Next streams',
|
||||||
pastStream: 'Latest streams',
|
pastStream: 'Latest streams',
|
||||||
|
nowPlaying: 'Now playing',
|
||||||
noStreams: 'No upcoming streams, check back soon.',
|
noStreams: 'No upcoming streams, check back soon.',
|
||||||
underscoreTagline: ['LEAVE THE', 'SURVEILLANCE ECONOMY', '— TOGETHER.'],
|
underscoreTagline: ['LEAVE THE', 'SURVEILLANCE ECONOMY', '— TOGETHER.'],
|
||||||
streamDateFuture: 'Going live at: ',
|
streamDateFuture: 'Going live at: ',
|
||||||
|
@ -1,20 +1,63 @@
|
|||||||
import { useEffect, useState } from 'preact/hooks'
|
import { useEffect, useState, useRef } from 'preact/hooks'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import ical from 'ical'
|
import ical from 'ical'
|
||||||
import config from '../data/config'
|
import config from '../data/config'
|
||||||
|
|
||||||
export const useCalendar = () => {
|
export const useEventStream = () => {
|
||||||
const [data, setData] = useState(null)
|
const [data, setData] = useState([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
const { data: responseData } = await axios.get(`${config.calendar}`)
|
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')
|
.filter(feedItem => feedItem.type === 'VEVENT')
|
||||||
.sort((a, b) => new Date(a.start) - new Date(b.start))
|
.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)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,73 +67,3 @@ export const useCalendar = () => {
|
|||||||
|
|
||||||
return { loading, data }
|
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
37
src/hooks/dom.js
Normal 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
9
src/hooks/utility.js
Normal 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]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user