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 { 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>
) )

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', midnightDarker: '#112B39',
offwhite: '#f6f4f5', offwhite: '#f6f4f5',
white: '#fff', white: '#ffffff',
highlight: '#03a59e', highlight: '#03a59e',
roseDarker: '#FEB9B3', roseDarker: '#FEB9B3',
rose: '#F1CFCD', rose: '#F1CFCD',

View File

@ -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>
) )
} }

View File

@ -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;
`

View File

@ -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>

View File

@ -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,
} }

View File

@ -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,
} }

View File

@ -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`

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 '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`}

View File

@ -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,

View File

@ -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);
}
` `

View File

@ -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: ',

View File

@ -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
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]
}