diff options
Diffstat (limited to 'src/views')
| -rw-r--r-- | src/views/App.js | 2 | ||||
| -rw-r--r-- | src/views/Clock.js | 4 | ||||
| -rw-r--r-- | src/views/Clocks.js | 4 | ||||
| -rw-r--r-- | src/views/Detail.js | 45 | ||||
| -rw-r--r-- | src/views/Gallery.js | 9 | ||||
| -rw-r--r-- | src/views/Graph.js | 16 | ||||
| -rw-r--r-- | src/views/Intro.js | 26 | ||||
| -rw-r--r-- | src/views/LandscapeWarning.js | 72 | ||||
| -rw-r--r-- | src/views/Legend.js | 2 |
9 files changed, 148 insertions, 32 deletions
diff --git a/src/views/App.js b/src/views/App.js index 1a3ca42..0458d1a 100644 --- a/src/views/App.js +++ b/src/views/App.js @@ -8,6 +8,7 @@ import { MTLLoader, OBJLoader } from "@hbis/three-obj-mtl-loader"; import Graph from "./Graph.js"; import Intro from "./Intro.js"; +import LandscapeWarning from "./LandscapeWarning.js"; export default function App() { const [db, setDb] = useState(null); @@ -27,6 +28,7 @@ export default function App() { <> {intro && <Intro onComplete={closeIntro} />} {!intro && db && <Graph db={db} />} + <LandscapeWarning /> </> ); } diff --git a/src/views/Clock.js b/src/views/Clock.js index fe5039a..de0026b 100644 --- a/src/views/Clock.js +++ b/src/views/Clock.js @@ -46,7 +46,7 @@ const minuteTransform = const secondTransform = "translateX(10.2vw) translateY(11.5vw) translateX(-100%) translateY(-100%) "; -export default function Clock({ utc }) { +export default function Clock({ utc, onLoad }) { const [hour, setHour] = useState({}); const [minute, setMinute] = useState({}); const [second, setSecond] = useState({}); @@ -90,7 +90,7 @@ export default function Clock({ utc }) { return ( <div style={clock}> - <img src="assets/img/clock/hour.png" style={hour} /> + <img src="assets/img/clock/hour.png" style={hour} onLoad={onLoad} /> <img src="assets/img/clock/minute.png" style={minute} /> <img src="assets/img/clock/second.png" style={second} /> </div> diff --git a/src/views/Clocks.js b/src/views/Clocks.js index d984f7b..133a70d 100644 --- a/src/views/Clocks.js +++ b/src/views/Clocks.js @@ -5,10 +5,10 @@ import React from "react"; import Clock from "./Clock.js"; -export default function Clocks() { +export default function Clocks({ onLoad }) { return ( <div className="clocks"> - <Clock /> + <Clock onLoad={onLoad} /> <Clock utc /> </div> ); diff --git a/src/views/Detail.js b/src/views/Detail.js index 772abb1..fb17c06 100644 --- a/src/views/Detail.js +++ b/src/views/Detail.js @@ -2,7 +2,7 @@ * Detail view, displaying text plus media */ -import React from "react"; +import React, { useRef, useEffect, useState, useCallback } from "react"; import Gallery from "./Gallery.js"; import Clocks from "./Clocks.js"; @@ -10,15 +10,45 @@ import Vimeo from "@u-wave/react-vimeo"; import { pad } from "../utils/index.js"; export default function Detail({ node, visible, onClose }) { + const ref = useRef(); + const contentRef = useRef(); + const [videoReady, setVideoReady] = useState(false); + + useEffect(() => { + if (!node) { + setVideoReady(false); + } + setTimeout(() => { + ref.current.scrollTo(0, -ref.current.scrollHeight); + if (contentRef.current) { + contentRef.current.scrollTo(0, -ref.current.scrollHeight); + } + }, 10); + }, [node]); + + const handleVideoReady = useCallback(() => { + if (node.data.object) { + setTimeout(() => { + setVideoReady(true); + }, 500); + } else { + setVideoReady(true); + } + }, [node]); + + const handleLoad = useCallback(() => { + ref.current.scrollTo(0, -ref.current.scrollHeight); + }); + if (!node) { - return <div className="detail" />; + return <div className="detail" ref={ref} />; } const { id, data } = node; const index = id + 1; return ( - <div className={visible ? "detail visible" : "detail"}> - <div className="content"> + <div className={visible ? "detail visible" : "detail"} ref={ref}> + <div className="content" ref={contentRef}> <div> <div className="title"> <div className="index">{pad(index)}</div> @@ -42,10 +72,10 @@ export default function Detail({ node, visible, onClose }) { <img src="/assets/img/close.svg" onClick={onClose} /> </div> {index === 33 ? ( - <Clocks /> + <Clocks onLoad={handleLoad} /> ) : data.type === "video" ? ( visible && ( - <div className="video"> + <div className={videoReady ? "video ready" : "video"}> <Vimeo video={data.images[0].uri.replace("player.", "")} autoplay @@ -53,11 +83,12 @@ export default function Detail({ node, visible, onClose }) { showByline={false} showPortrait={false} showTitle={false} + onReady={handleVideoReady} /> </div> ) ) : ( - <Gallery images={data.images} visible={visible} /> + <Gallery images={data.images} visible={visible} onLoad={handleLoad} /> )} </div> </div> diff --git a/src/views/Gallery.js b/src/views/Gallery.js index e2652df..7681e3b 100644 --- a/src/views/Gallery.js +++ b/src/views/Gallery.js @@ -5,7 +5,7 @@ import React, { useState, useEffect } from "react"; import { mod } from "../utils/index.js"; -export default function Gallery({ images, visible }) { +export default function Gallery({ images, visible, onLoad }) { const hasItems = !!images?.length; const oneItem = images?.length === 1; @@ -15,26 +15,31 @@ export default function Gallery({ images, visible }) { setIndex(0); setOpacity(0); setTimeout(() => setOpacity(1), 500); + onLoad(); }, [images]); function previous() { setOpacity(0); + onLoad(); setTimeout(() => setIndex(mod(index - 1, images.length)), 200); // setTimeout(() => setOpacity(1), 500); } function next() { setOpacity(0); + onLoad(); setTimeout(() => setIndex(mod(index + 1, images.length)), 200); // setTimeout(() => setOpacity(1), 500); } function nextOrWrap() { if (oneItem) return; setOpacity(0); + onLoad(); setTimeout(() => setIndex(mod(index + 1, images.length)), 200); // setTimeout(() => setOpacity(1), 500); } function appear() { setOpacity(1); + onLoad(); } if (!hasItems) { @@ -53,7 +58,7 @@ export default function Gallery({ images, visible }) { /> )} </div> - <div className="buttons"> + <div className="buttons arrows"> {!oneItem && ( <img src="/assets/img/arrow-back.svg" diff --git a/src/views/Graph.js b/src/views/Graph.js index 21972e8..0920f76 100644 --- a/src/views/Graph.js +++ b/src/views/Graph.js @@ -23,16 +23,16 @@ export default function Graph({ db }) { /** Build the graph */ useEffect(() => { - setGraph( - buildGraph({ - db, - handlers: { - click: handleClick, - }, - }) - ); + const graph = buildGraph({ + db, + handlers: { + click: handleClick, + }, + }); + setGraph(graph); setTimeout(() => { setIntroCurtainDone(true); + graph.onLoad(); setTimeout(() => { setIntroDone(true); }, 4000); diff --git a/src/views/Intro.js b/src/views/Intro.js index bf52d33..d27d34b 100644 --- a/src/views/Intro.js +++ b/src/views/Intro.js @@ -18,6 +18,13 @@ export default function Intro({ onComplete }) { }, 200); }, []); + const handleReady = useCallback((player) => { + setPlayer(player); + if (playing) { + player.play(); + } + }, []); + return ( <div className={done ? "intro done" : "intro"}> <Vimeo @@ -27,7 +34,7 @@ export default function Intro({ onComplete }) { showPortrait={false} showTitle={false} style={videoSize} - onReady={setPlayer} + onReady={handleReady} onEnd={handleClose} /> <div @@ -35,21 +42,20 @@ export default function Intro({ onComplete }) { className={playing ? "intro-image playing" : "intro-image"} onClick={() => { setPlaying(true); - player.play(); + player && player.play(); }} /> + {playing && ( + <img + className="close" + src="/assets/img/close.svg" + onClick={handleClose} + /> + )} </div> ); } -/* - <img - className="close" - src="/assets/img/close.svg" - onClick={handleClose} - /> - */ - const coverWindow = () => { const videoRatio = 1.777; const screenRatio = window.innerWidth / window.innerHeight; diff --git a/src/views/LandscapeWarning.js b/src/views/LandscapeWarning.js new file mode 100644 index 0000000..389e8a6 --- /dev/null +++ b/src/views/LandscapeWarning.js @@ -0,0 +1,72 @@ +/** + * Instruction to rotate the phone to landscape + */ + +import React, { Component } from "react"; + +import { isMobile, isiPhone } from "../utils/index.js"; + +const PhoneIcon = ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + className="phone-icon" + > + <path d="M15.5 1h-8C6.12 1 5 2.12 5 3.5v17C5 21.88 6.12 23 7.5 23h8c1.38 0 2.5-1.12 2.5-2.5v-17C18 2.12 16.88 1 15.5 1zm-4 21c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4.5-4H7V4h9v14z" /> + </svg> +); + +const RotateIcon = ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + className="rotate-icon" + > + <path d="M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z" /> + </svg> +); + +export default class LandscapeWarning extends Component { + state = { + landscape: !isMobile || window.innerWidth > window.innerHeight, + }; + + constructor(props) { + super(props); + this.handleResize = this.handleResize.bind(this); + if (isMobile) { + window.addEventListener("resize", this.handleResize); + setTimeout(this.handleResize, 100); + } + } + + handleResize() { + const landscape = !isMobile || window.innerWidth > window.innerHeight; + if (landscape !== this.state.landscape) { + this.setState({ landscape }); + } + } + + render() { + const { landscape } = this.state; + if (landscape) return null; + return ( + <div className="landscape-warning"> + {RotateIcon} + {PhoneIcon} + {isiPhone && ( + <div className="landscape-message"> + {"Please tap "} + <small>A</small> + {"A and Hide Toolbar"} + <br /> + {"then rotate your device."} + </div> + )} + {!isiPhone && ( + <div className="landscape-message">{"Please rotate your device"}</div> + )} + </div> + ); + } +} diff --git a/src/views/Legend.js b/src/views/Legend.js index eac860c..bb56490 100644 --- a/src/views/Legend.js +++ b/src/views/Legend.js @@ -21,7 +21,7 @@ var categories = [ export default function Legend({ visible, selected, onSelect }) { return ( - <div className="legend" style={{ opacity: visible ? 1 : 0 }}> + <div className={visible ? "legend visible" : "legend"}> {selected && ( <div className="removeSelection" onClick={() => onSelect(selected)}> {"View all"} |
