summaryrefslogtreecommitdiff
path: root/src/views
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 /src/views
parent15cc8f97b68569332b19c9e716f02532a66b36c7 (diff)
details and gallery
Diffstat (limited to 'src/views')
-rw-r--r--src/views/App.js42
-rw-r--r--src/views/Detail.js65
-rw-r--r--src/views/Gallery.js75
3 files changed, 163 insertions, 19 deletions
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);