diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2021-08-24 18:40:19 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2021-08-24 18:40:19 +0200 |
| commit | 1cc630da4247e75a18629d960768d06239b0175b (patch) | |
| tree | 15a21324740672b3dcfde0ad7123f5f8b20434a5 | |
| parent | 15cc8f97b68569332b19c9e716f02532a66b36c7 (diff) | |
details and gallery
| -rw-r--r-- | .babelrc | 27 | ||||
| -rw-r--r-- | load_spreadsheet.js | 1 | ||||
| -rw-r--r-- | public/assets/css/css.css | 97 | ||||
| -rw-r--r-- | public/assets/css/fonts.css | 11 | ||||
| -rw-r--r-- | public/assets/fonts/merriweather/Merriweather-Italic.ttf | bin | 0 -> 142548 bytes | |||
| -rw-r--r-- | public/assets/fonts/merriweather/Merriweather-Regular.ttf | bin | 0 -> 149092 bytes | |||
| -rw-r--r-- | public/assets/img/arrow-back.svg | 1 | ||||
| -rw-r--r-- | public/assets/img/arrow-forward.svg | 1 | ||||
| -rw-r--r-- | public/assets/img/close.svg | 1 | ||||
| -rw-r--r-- | src/graph.js | 2 | ||||
| -rw-r--r-- | src/index.js | 21 | ||||
| -rw-r--r-- | src/views/App.js | 42 | ||||
| -rw-r--r-- | src/views/Detail.js | 65 | ||||
| -rw-r--r-- | src/views/Gallery.js | 75 | ||||
| -rw-r--r-- | templates/_header.liquid | 1 |
15 files changed, 308 insertions, 37 deletions
diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..ca2b68b --- /dev/null +++ b/.babelrc @@ -0,0 +1,27 @@ +{ + "presets": [ + ["@babel/preset-env", { + "debug": false, + "modules": false, + "useBuiltIns": false + }], + "@babel/preset-react" + ], + "plugins": [ + "@babel/plugin-transform-runtime", + ["@babel/plugin-proposal-private-property-in-object", { "loose": true }], + ["@babel/plugin-proposal-private-methods", { "loose": true }], + [ "@babel/plugin-proposal-class-properties", { "loose": true } ], + "transform-async-to-generator", + ["module-resolver", { + "root": ["./frontend"], + "alias": { + } + }] + ], + "env": { + "production": { + "presets": [] + } + } +} diff --git a/load_spreadsheet.js b/load_spreadsheet.js index 3bada97..7c06477 100644 --- a/load_spreadsheet.js +++ b/load_spreadsheet.js @@ -40,6 +40,7 @@ async function main() { __index: index, id: "page_" + index, title: row.Title, + type: "image", }; // loop over the tags... let tagIndex = 0; diff --git a/public/assets/css/css.css b/public/assets/css/css.css index 3d4f9e0..fd62f45 100644 --- a/public/assets/css/css.css +++ b/public/assets/css/css.css @@ -6,6 +6,7 @@ body { height: 100%; overflow: hidden; background: black; + font-family: "Merriweather", serif; } .scene-tooltip { background: black; @@ -17,3 +18,99 @@ body { line-height: 1.3; font-family: serif !important; } + +/** Detail view */ + +.detail { + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + transition: opacity 0.2s; + opacity: 0; + background: rgba(0, 0, 0, 0.75); + color: white; + display: flex; + flex-direction: row; +} +.detail.visible { + opacity: 1; + pointer-events: auto; +} +.detail > div { + height: 100vh; + width: 50vw; +} +.detail .content > div { + padding: 3rem 5rem 6rem 5rem; +} +.detail .media { + padding: 3rem 5rem; +} + +.detail .content { + overflow-x: hidden; + overflow-y: scroll; +} +.detail .content::-webkit-scrollbar { + width: 0; + opacity: 0; +} +.detail .content > div { + max-width: 600px; +} +.detail .title { + font-size: 1.25rem; + line-height: 1.5; + margin-bottom: 2rem; + text-transform: capitalize; + font-variant: small-caps; +} +.detail .title .index { + margin-bottom: 1rem; +} +.detail .citation { + font-size: 1rem; + line-height: 1.5; + margin-bottom: 2rem; +} +.detail .description { + font-size: 1rem; + line-height: 1.75; +} +.detail .description p { + text-indent: 2rem; +} +.detail .description p:first-of-type { + text-indent: 0; +} + +/** Image galleries */ +.gallery { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; +} +.gallery .image img { + cursor: pointer; + max-height: calc(100vh - 12rem); + max-width: 100%; + transition: opacity 100ms; +} +.buttons { + width: 100%; + text-align: right; +} +.buttons.close { + margin-bottom: 1rem; +} +.buttons img { + cursor: pointer; + height: 1.5rem; +} +.gallery .buttons { + margin-top: 1rem; +} diff --git a/public/assets/css/fonts.css b/public/assets/css/fonts.css new file mode 100644 index 0000000..f1e8429 --- /dev/null +++ b/public/assets/css/fonts.css @@ -0,0 +1,11 @@ +@font-face { + font-family: "Merriweather"; + src: url("../fonts/merriweather/Merriweather-Regular.ttf"); + font-style: normal; +} + +@font-face { + font-family: "Merriweather"; + src: url("../fonts/merriweather/Merriweather-Italic.ttf"); + font-style: italic; +} diff --git a/public/assets/fonts/merriweather/Merriweather-Italic.ttf b/public/assets/fonts/merriweather/Merriweather-Italic.ttf Binary files differnew file mode 100644 index 0000000..179acf3 --- /dev/null +++ b/public/assets/fonts/merriweather/Merriweather-Italic.ttf diff --git a/public/assets/fonts/merriweather/Merriweather-Regular.ttf b/public/assets/fonts/merriweather/Merriweather-Regular.ttf Binary files differnew file mode 100644 index 0000000..18da9e5 --- /dev/null +++ b/public/assets/fonts/merriweather/Merriweather-Regular.ttf diff --git a/public/assets/img/arrow-back.svg b/public/assets/img/arrow-back.svg new file mode 100644 index 0000000..6d00888 --- /dev/null +++ b/public/assets/img/arrow-back.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M11.67 3.87L9.9 2.1 0 12l9.9 9.9 1.77-1.77L3.54 12z"/></svg>
\ No newline at end of file diff --git a/public/assets/img/arrow-forward.svg b/public/assets/img/arrow-forward.svg new file mode 100644 index 0000000..6bd4748 --- /dev/null +++ b/public/assets/img/arrow-forward.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><polygon points="6.23,20.23 8,22 18,12 8,2 6.23,3.77 14.46,12"/></g></svg>
\ No newline at end of file diff --git a/public/assets/img/close.svg b/public/assets/img/close.svg new file mode 100644 index 0000000..0b3a55f --- /dev/null +++ b/public/assets/img/close.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
\ No newline at end of file diff --git a/src/graph.js b/src/graph.js index fdac979..4ed953a 100644 --- a/src/graph.js +++ b/src/graph.js @@ -105,7 +105,7 @@ export default function buildGraph(db, handlers) { // graph.d3Force("charge").strength(-150); // camera orbit - const distance = 250; + const distance = 415; let angle = 0; graph.cameraPosition({ x: distance * Math.sin(angle), diff --git a/src/index.js b/src/index.js index c51ecd1..c084565 100644 --- a/src/index.js +++ b/src/index.js @@ -2,23 +2,12 @@ * No.6092 site for Charles Stankievech */ -import buildGraph from "./graph.js"; +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./views/App.js"; -async function main() { - const db = await loadDB(); - - function handleClick(node) { - console.log(node); - } - - buildGraph(db, { - click: handleClick, - }); -} - -async function loadDB() { - const request = await fetch("/assets/db.json"); - return await request.json(); +function main() { + ReactDOM.render(<App />, document.querySelector("#app")); } main(); diff --git a/src/views/App.js b/src/views/App.js new file mode 100644 index 0000000..c5a7f83 --- /dev/null +++ b/src/views/App.js @@ -0,0 +1,42 @@ +/** + * Main React app logic + */ + +import React, { useState, useEffect, useCallback } from "react"; + +import Detail from "./Detail.js"; +import buildGraph from "../graph.js"; + +export default function App() { + const [db, setDb] = useState(null); + const [node, setNode] = useState(null); + const [detailVisible, setDetailVisible] = useState(null); + + useEffect(async () => { + const newDb = await loadDB(); + setDb(newDb); + buildGraph(newDb, { + click: handleClick, + }); + }, []); + + const handleClick = useCallback((node) => { + setNode(node); + setDetailVisible(true); + }); + + const handleClose = useCallback((node) => { + setDetailVisible(false); + }); + + return ( + <div> + <Detail node={node} visible={detailVisible} onClose={handleClose} /> + </div> + ); +} + +async function loadDB() { + const request = await fetch("/assets/db.json"); + return await request.json(); +} diff --git a/src/views/Detail.js b/src/views/Detail.js index 51598c1..0a756c9 100644 --- a/src/views/Detail.js +++ b/src/views/Detail.js @@ -2,30 +2,57 @@ * Detail view, displaying text plus media */ -export default function Detail({ node }) { - const index = id + 1; +import React from "react"; + +import Gallery from "./Gallery.js"; + +export default function Detail({ node, visible, onClose }) { + if (!node) { + return <div className="detail" />; + } + const { id, data } = node; + const index = id + 1; return ( - <div className="detail"> + <div className={visible ? "detail visible" : "detail"}> <div className="content"> - <div className="title"> - {index} - <br /> - {data.author} - <br /> - {data.title} - <br /> + <div> + <div className="title"> + <div className="index">{pad(index)}</div> + {data.author && ( + <div dangerouslySetInnerHTML={{ __html: data.author }} /> + )} + <div dangerouslySetInnerHTML={{ __html: data.title }} /> + </div> + <div + className="citation" + dangerouslySetInnerHTML={{ __html: data.citation }} + /> + <div + className="description" + dangerouslySetInnerHTML={{ __html: data.description }} + /> + </div> + </div> + <div className="media"> + <div className="buttons close"> + <img src="/assets/img/close.svg" onClick={onClose} /> </div> - <div - className="caption" - dangerouslySetInnerHTML={{ __html: data.caption }} - /> - <div - className="description" - dangerouslySetInnerHTML={{ __html: data.description }} - /> + {node.type === "video" ? ( + "video" + ) : ( + <Gallery images={data.images} visible={visible} /> + )} </div> - <div className="media"></div> </div> ); } + +const pad = (value) => (value < 10 ? "0" + value : value); +const capitalizeWord = (text = "") => + text ? text.charAt(0).toUpperCase() + text.slice(1) : ""; +const capitalize = (text = "") => + String(text || "") + .split(" ") + .map(capitalizeWord) + .join(" "); diff --git a/src/views/Gallery.js b/src/views/Gallery.js new file mode 100644 index 0000000..7bdf18b --- /dev/null +++ b/src/views/Gallery.js @@ -0,0 +1,75 @@ +/** + * Detail view, displaying text plus media + */ + +import React, { useState, useEffect } from "react"; + +export default function Gallery({ images, visible }) { + const hasItems = !!images?.length; + const oneItem = images?.length === 1; + + const [index, setIndex] = useState(0); + const [opacity, setOpacity] = useState(0); + useEffect(() => { + setIndex(0); + setOpacity(0); + setTimeout(() => setOpacity(1), 500); + }, [images]); + + function previous() { + setOpacity(0); + setTimeout(() => setIndex(Math.max(0, index - 1)), 100); + // setTimeout(() => setOpacity(1), 500); + } + function next() { + setOpacity(0); + setTimeout(() => setIndex(Math.min(images.length - 1, index + 1)), 100); + // setTimeout(() => setOpacity(1), 500); + } + function nextOrWrap() { + if (oneItem) return; + setOpacity(0); + setTimeout(() => setIndex(mod(index + 1, images.length)), 100); + // setTimeout(() => setOpacity(1), 500); + } + function appear() { + setOpacity(1); + } + + if (!hasItems) { + return <div className="gallery" style={{ opacity: 0 }} />; + } + + return ( + <div className="gallery" style={{ opacity: visible ? 1 : 0 }}> + <div className="image"> + {visible && !!images[index] && ( + <img + src={images[index].uri} + onClick={nextOrWrap} + onLoad={appear} + style={{ opacity }} + /> + )} + </div> + <div className="buttons"> + {!oneItem && ( + <img + src="/assets/img/arrow-back.svg" + onClick={previous} + style={{ opacity: index > 0 ? 1 : 0 }} + /> + )} + {!oneItem && ( + <img + src="/assets/img/arrow-forward.svg" + onClick={next} + style={{ opacity: index < images.length - 1 ? 1 : 0 }} + /> + )} + </div> + </div> + ); +} + +const mod = (n, m) => n - m * Math.floor(n / m); diff --git a/templates/_header.liquid b/templates/_header.liquid index 349ab58..0634722 100644 --- a/templates/_header.liquid +++ b/templates/_header.liquid @@ -23,7 +23,6 @@ <meta name="apple-mobile-web-app-capable" content="no"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <link rel="shortcut icon" href="/favicon.ico" /> - <link href="https://fonts.googleapis.com/css?family=Roboto+Mono:300,400,500|Roboto:300,300i,400,400i,700,700i,900,900i" rel="stylesheet" data-noprefix> {% if meta.production %} {% else %} |
