summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2021-08-24 18:40:19 +0200
committerJules Laplace <julescarbon@gmail.com>2021-08-24 18:40:19 +0200
commit1cc630da4247e75a18629d960768d06239b0175b (patch)
tree15a21324740672b3dcfde0ad7123f5f8b20434a5
parent15cc8f97b68569332b19c9e716f02532a66b36c7 (diff)
details and gallery
-rw-r--r--.babelrc27
-rw-r--r--load_spreadsheet.js1
-rw-r--r--public/assets/css/css.css97
-rw-r--r--public/assets/css/fonts.css11
-rw-r--r--public/assets/fonts/merriweather/Merriweather-Italic.ttfbin0 -> 142548 bytes
-rw-r--r--public/assets/fonts/merriweather/Merriweather-Regular.ttfbin0 -> 149092 bytes
-rw-r--r--public/assets/img/arrow-back.svg1
-rw-r--r--public/assets/img/arrow-forward.svg1
-rw-r--r--public/assets/img/close.svg1
-rw-r--r--src/graph.js2
-rw-r--r--src/index.js21
-rw-r--r--src/views/App.js42
-rw-r--r--src/views/Detail.js65
-rw-r--r--src/views/Gallery.js75
-rw-r--r--templates/_header.liquid1
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
new file mode 100644
index 0000000..179acf3
--- /dev/null
+++ b/public/assets/fonts/merriweather/Merriweather-Italic.ttf
Binary files differ
diff --git a/public/assets/fonts/merriweather/Merriweather-Regular.ttf b/public/assets/fonts/merriweather/Merriweather-Regular.ttf
new file mode 100644
index 0000000..18da9e5
--- /dev/null
+++ b/public/assets/fonts/merriweather/Merriweather-Regular.ttf
Binary files differ
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 %}