diff --git a/TODO.md b/TODO.md
index 3e11d04..54bad08 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,7 +1,9 @@
- [ ] Resume/Projects portfolio
- [ ] Blog
-- [ ] tags
+- [x] tags
- [ ] photo stories/collection pages
- [ ] typescript (w/ graphql codegen)
+- [ ] CMS
- [x] gallery sort and filter preserved in url (use-query or whatever?)
-- [ ] nav next/prev buttons use saved gallery sort and filter
+- [x] nav next/prev buttons use saved gallery sort and filter
+- [ ] bug: "gallery" buttin in nav should reset filter and sort selections
diff --git a/gatsby-node.js b/gatsby-node.js
index 6c1ce53..9755f2b 100644
--- a/gatsby-node.js
+++ b/gatsby-node.js
@@ -220,16 +220,16 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
);
edges.forEach(({ node }, index) => {
- const nextImage =
- index === edges.length - 1 ? null : edges[index + 1].node.base;
- const prevImage = index === 0 ? null : edges[index - 1].node.base;
+ // const nextImage =
+ // index === edges.length - 1 ? null : edges[index + 1].node.base;
+ // const prevImage = index === 0 ? null : edges[index - 1].node.base;
const page = {
path: `photogallery/${node.base}`,
component: galleryImageTemplate,
context: {
imageFilename: node.base,
- nextImage,
- prevImage,
+ // nextImage,
+ // prevImage,
},
};
createPage(page);
diff --git a/src/components/GalleryImage/GalleryImage.js b/src/components/GalleryImage/GalleryImage.js
index e222a21..723755a 100644
--- a/src/components/GalleryImage/GalleryImage.js
+++ b/src/components/GalleryImage/GalleryImage.js
@@ -21,6 +21,7 @@ import {
getHelmetSafeBodyStyle,
hasName,
getCanonicalSize,
+ getGalleryPageUrl,
} from "../../utils";
import MetadataItem from "./MetadataItem";
@@ -35,33 +36,64 @@ const logKeyShortcut = (keyCode) => {
}
};
-const GalleryImage = ({ data, pageContext }) => {
+const GalleryImage = ({
+ data,
+ location: {
+ state: { sortedImageList, currentIndex, filterKeyword, sortKey },
+ },
+}) => {
const image = data.file;
const ar = getAspectRatio(image);
const [zoom, setZoom] = useState(false);
+ const nextIndex =
+ sortedImageList && currentIndex < sortedImageList.length
+ ? currentIndex + 1
+ : null;
+ const prevIndex =
+ sortedImageList && currentIndex > 0 ? currentIndex - 1 : null;
+
+ const nextImage = sortedImageList && sortedImageList[nextIndex];
+ const prevImage = sortedImageList && sortedImageList[prevIndex];
+
React.useEffect(() => {
const keyListener = (e) => {
switch (e.code) {
case "ArrowRight": {
logKeyShortcut(e.code);
- if (pageContext.nextImage) {
- navigate(`/photogallery/${pageContext.nextImage}/`);
+ if (nextImage) {
+ navigate(`/photogallery/${nextImage}/`, {
+ state: {
+ currentIndex: currentIndex + 1,
+ sortedImageList,
+ filterKeyword,
+ sortKey,
+ },
+ });
}
return;
}
case "ArrowLeft": {
logKeyShortcut(e.code);
- if (pageContext.prevImage) {
- navigate(`/photogallery/${pageContext.prevImage}/`);
+ if (prevImage) {
+ navigate(`/photogallery/${prevImage}/`, {
+ state: {
+ currentIndex: currentIndex - 1,
+ sortedImageList,
+ filterKeyword,
+ sortKey,
+ },
+ });
}
return;
}
case "Escape":
case "KeyG": {
logKeyShortcut(e.code);
- navigate(`/photogallery/#${image.base}`);
+ navigate(
+ getGalleryPageUrl({ keyword: filterKeyword, sortKey }, image.base)
+ );
}
}
};
@@ -69,7 +101,15 @@ const GalleryImage = ({ data, pageContext }) => {
return () => {
document.removeEventListener("keydown", keyListener);
};
- }, [pageContext, image.base]);
+ }, [
+ nextImage,
+ prevImage,
+ image.base,
+ currentIndex,
+ sortedImageList,
+ filterKeyword,
+ sortKey,
+ ]);
const name = getName(image);
const { meta, dateTaken: dt } = getMeta(image);
@@ -118,22 +158,37 @@ const GalleryImage = ({ data, pageContext }) => {
gallery esc
- {pageContext.prevImage && (
+ {prevImage && (
previous ⭠
)}
- {pageContext.nextImage && (
+ {nextImage && (
next ⭢
diff --git a/src/components/MasonryGallery.js b/src/components/MasonryGallery.js
index d0e2cbc..b16c29f 100644
--- a/src/components/MasonryGallery.js
+++ b/src/components/MasonryGallery.js
@@ -13,11 +13,12 @@ const MasonryGallery = ({
aspectsByBreakpoint: aspectTargetsByBreakpoint,
debugHue,
debugRating,
+ linkState,
}) => {
const breakpoints = React.useMemo(
() => R.pick(R.keys(aspectTargetsByBreakpoint), themeBreakpoints),
[aspectTargetsByBreakpoint]
- );
+ );
const { breakpoint } = useBreakpoint(breakpoints, "sm");
@@ -65,81 +66,89 @@ const MasonryGallery = ({
[aspectRatios, targetAspect]
);
+ const sortedImageList = React.useMemo(
+ () => images.map((image) => image.base),
+ [images]
+ );
+
let cursor = 0;
return (
<>
- {/* {breakpoint} */}
-
- {images.map((image, i) => {
- let currentRow = rows[cursor];
- if (rows[i]) {
- cursor = i;
- currentRow = rows[i];
- }
- const rowAspectRatioSum = currentRow.aspect;
- const ar = getAspectRatio(image);
- let width;
- let height = `calc(100vw / ${rowAspectRatioSum} - 10px)`;
- if (rowAspectRatioSum < targetAspect * 0.66) {
- // incomplete row, render stuff at "ideal" sizes instead of filling width
- width = `calc(100vw / ${targetAspect / ar})`;
- height = "unset";
- } else {
- const widthNumber = ((ar / rowAspectRatioSum) * 100).toFixed(7);
- width = `${widthNumber}%`;
- }
- return (
-
+ {images.map((image, i) => {
+ let currentRow = rows[cursor];
+ if (rows[i]) {
+ cursor = i;
+ currentRow = rows[i];
+ }
+ const rowAspectRatioSum = currentRow.aspect;
+ const ar = getAspectRatio(image);
+ let width;
+ let height = `calc(100vw / ${rowAspectRatioSum} - 10px)`;
+ if (rowAspectRatioSum < targetAspect * 0.66) {
+ // incomplete row, render stuff at "ideal" sizes instead of filling width
+ width = `calc(100vw / ${targetAspect / ar})`;
+ height = "unset";
+ } else {
+ const widthNumber = ((ar / rowAspectRatioSum) * 100).toFixed(7);
+ width = `${widthNumber}%`;
+ }
+ return (
+
- {debugHue && (
-
- hsl(
- {image.fields.imageMeta.dominantHue[0]},{" "}
- {(image.fields.imageMeta.dominantHue[1] * 100).toFixed(2)}%,{" "}
- {(image.fields.imageMeta.dominantHue[2] * 100).toFixed(2)}% )
-
- )}
- {debugRating && (
-
- rating: {image.fields.imageMeta.meta.Rating}
-
- )}
-
-
- );
- })}
-
+ : "black",
+ }}
+ to={`/photogallery/${image.base}`}
+ >
+ {debugHue && (
+
+ hsl(
+ {image.fields.imageMeta.dominantHue[0]},{" "}
+ {(image.fields.imageMeta.dominantHue[1] * 100).toFixed(2)}%,{" "}
+ {(image.fields.imageMeta.dominantHue[2] * 100).toFixed(2)}% )
+
+ )}
+ {debugRating && (
+
+ rating: {image.fields.imageMeta.meta.Rating}
+
+ )}
+
+
+ );
+ })}
+
>
);
};
diff --git a/src/pages/photogallery/[...].js b/src/pages/photogallery/[...].js
index 2cfd54a..17b74ee 100644
--- a/src/pages/photogallery/[...].js
+++ b/src/pages/photogallery/[...].js
@@ -7,6 +7,7 @@ import { Picker, Item } from "@adobe/react-spectrum";
import MasonryGallery from "../../components/MasonryGallery";
import KeywordsPicker from "../../components/KeywordsPicker";
+import { getGalleryPageUrl } from "../../utils";
const SORT_KEYS = {
hue: ["fields", "imageMeta", "vibrantHue"],
@@ -15,27 +16,10 @@ const SORT_KEYS = {
date: [],
};
-const getUrl = ({ keyword, sortKey }) => {
- const url = new URL(window.location.toString());
- if (keyword !== undefined) {
- if (keyword === null) {
- url.searchParams.delete("filter");
- } else {
- url.searchParams.set("filter", keyword);
- }
- }
- if (sortKey !== undefined) {
- if (sortKey === "rating") {
- url.searchParams.delete("sort");
- } else {
- url.searchParams.set("sort", sortKey);
- }
- }
- return url.toString();
-};
-
const GalleryPage = ({ data }) => {
- const [keyword, _setKeyword] = React.useState(null);
+ const hash = window.location.hash.replace("#", "");
+
+ const [filterKeyword, _setKeyword] = React.useState(null);
const [sortKey, _setSortKey] = React.useState("rating");
const showDebug =
typeof window !== "undefined" &&
@@ -51,10 +35,13 @@ const GalleryPage = ({ data }) => {
// do nothing
}
_setKeyword(newKeyword);
- localStorage?.setItem("photogallery.keyword", newKeyword);
- window.history.replaceState(null, "", getUrl({ keyword: newKeyword }));
+ window.history.replaceState(
+ null,
+ "",
+ getGalleryPageUrl({ keyword: newKeyword, sortKey }, hash)
+ );
},
- [_setKeyword]
+ [_setKeyword, sortKey, hash]
);
const setSortKey = React.useCallback(
@@ -67,48 +54,47 @@ const GalleryPage = ({ data }) => {
// do nothing
}
_setSortKey(newSortKey);
- localStorage?.setItem("photogallery.sortkey2", newSortKey);
- window.history.replaceState(null, "", getUrl({ sortKey: newSortKey }));
+ window.history.replaceState(
+ null,
+ "",
+ getGalleryPageUrl({ sortKey: newSortKey, keyword: filterKeyword }, hash)
+ );
},
- [_setSortKey]
+ [_setSortKey, filterKeyword, hash]
);
- React.useEffect(() => {
- const url = new URL(window.location.toString());
-
- const sortKeyFromUrl = url.searchParams.get("sort");
- const sortKeyFromStorage = localStorage.getItem("photogallery.sortkey2");
- if (sortKeyFromUrl || sortKeyFromStorage) {
- setSortKey(sortKeyFromUrl || sortKeyFromStorage);
- }
-
- const filterKeyFromUrl = url.searchParams.get("filter");
- const filterKeyFromStorage = localStorage.getItem("photogallery.keyword");
- if (filterKeyFromUrl || filterKeyFromStorage !== "null") {
- setKeyword(filterKeyFromUrl || filterKeyFromStorage);
- }
- }, [setSortKey, setKeyword]);
-
const scrollIntoView = React.useCallback(() => {
- if (!window.location.hash) {
+ if (!hash) {
return;
}
- const el = document.getElementById(window.location.hash.split("#")[1]);
+ const el = document.getElementById(hash);
if (!el) {
return;
}
el.scrollIntoView({
block: "center",
});
- }, []);
+ }, [hash]);
React.useEffect(() => {
+ const url = new URL(window.location.toString());
+
+ const sortKeyFromUrl = url.searchParams.get("sort");
+ if (sortKeyFromUrl) {
+ _setSortKey(sortKeyFromUrl, false);
+ }
+
+ const filterKeyFromUrl = url.searchParams.get("filter");
+ if (filterKeyFromUrl) {
+ _setKeyword(filterKeyFromUrl, false);
+ }
+
// hacky but it works for now
setTimeout(() => {
// don't scroll into view if user got here with back button
scrollIntoView();
}, 100);
- }, [scrollIntoView]);
+ }, [setSortKey, setKeyword, scrollIntoView]);
const images = React.useMemo(
() =>
@@ -124,16 +110,16 @@ const GalleryPage = ({ data }) => {
return -1 * (date1.getTime() - date2.getTime());
})
: R.sort(R.descend(R.path(SORT_KEYS[sortKey]))),
- keyword
+ filterKeyword
? R.filter((image) =>
R.includes(
- keyword,
+ filterKeyword,
R.path(["fields", "imageMeta", "meta", "Keywords"], image)
)
)
: R.identity
)(data.allFile.nodes),
- [data, sortKey, keyword]
+ [data, sortKey, filterKeyword]
);
return (
@@ -183,7 +169,7 @@ const GalleryPage = ({ data }) => {
// "sunset",
]}
onChange={setKeyword}
- value={keyword}
+ value={filterKeyword}
/>
{
debugHue={sortKey === "hue_debug"}
debugRating={sortKey === "rating" && showDebug}
images={images}
+ linkState={{
+ sortKey,
+ filterKeyword,
+ }}
/>
>
);
diff --git a/src/utils.js b/src/utils.js
index ec644fa..98630ec 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -79,3 +79,28 @@ export const getShutterFractionFromExposureTime = (exposureTime) => {
}
return `${numerator}/${denominator}`;
};
+
+export const getGalleryPageUrl = (
+ { keyword, sortKey },
+ hash
+) => {
+ const url = new URL(`${window.location.origin}/photogallery/`);
+ if (keyword !== undefined) {
+ if (keyword === null) {
+ url.searchParams.delete("filter");
+ } else {
+ url.searchParams.set("filter", keyword);
+ }
+ }
+ if (sortKey !== undefined) {
+ if (sortKey === "rating") {
+ url.searchParams.delete("sort");
+ } else {
+ url.searchParams.set("sort", sortKey);
+ }
+ }
+ if (hash) {
+ url.hash = hash;
+ }
+ return url.href.toString().replace(url.origin, '');
+};