Personal-Website/src/pages/photogallery.tsx

280 lines
7.5 KiB
TypeScript

import * as React from "react";
import * as R from "ramda";
import { graphql, PageProps } from "gatsby";
import { Helmet } from "react-helmet";
// import { Picker, Item } from "@adobe/react-spectrum";
import MasonryGallery from "../components/MasonryGallery";
import KeywordsPicker from "../components/KeywordsPicker";
import { getGalleryPageUrl, getHelmetSafeBodyStyle } from "../utils";
import Nav from "../components/Nav";
import { Item, Select } from "../components/Select";
const SORT_KEYS = {
hue: ["fields", "imageMeta", "vibrantHue"],
rating: ["fields", "imageMeta", "meta", "Rating"],
hue_debug: ["fields", "imageMeta", "dominantHue", 0],
date: [],
} as const;
export type GalleryImage =
Queries.GalleryPageQueryQuery["allFile"]["nodes"][number];
const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
const hash =
typeof window !== "undefined" ? window.location.hash.replace("#", "") : "";
const [hashCleared, setHashCleared] = React.useState(false); // eslint-disable-line no-unused-vars
// ^ used just to force a re-render with the cleared hash value (I know, it's a smell for sure)
const [filterKeyword, _setKeyword] = React.useState(null as string | null);
const [sortKey, _setSortKey] = React.useState("rating" as string);
const showDebug =
typeof window !== "undefined" &&
window.location.search.includes("debug=true");
const setKeyword = React.useCallback(
(newKeyword: string | null) => {
if (newKeyword) {
try {
window.plausible("Filter Keyword", {
props: { keyword: newKeyword },
});
} catch (e) {
// do nothing
}
}
_setKeyword(newKeyword);
window.history.replaceState(
null,
"",
getGalleryPageUrl({ keyword: newKeyword, sortKey }, hash)
);
},
[_setKeyword, sortKey, hash]
);
const setSortKey = React.useCallback(
(newSortKey: string) => {
try {
window.plausible("Sort Gallery", {
props: { key: newSortKey },
});
} catch (e) {
// do nothing
}
_setSortKey(newSortKey);
window.history.replaceState(
null,
"",
getGalleryPageUrl({ sortKey: newSortKey, keyword: filterKeyword }, hash)
);
},
[_setSortKey, filterKeyword, hash]
);
const removeHash = React.useCallback(() => {
const url = new URL(
typeof window !== "undefined"
? window.location.href.toString()
: "https://chuckdries.com/photogallery/"
);
url.hash = "";
window.history.replaceState(null, "", url.href.toString());
window.removeEventListener("wheel", removeHash);
setHashCleared(true);
}, []);
const scrollIntoView = React.useCallback(() => {
if (!hash) {
return;
}
const el = document.getElementById(hash);
if (!el) {
return;
}
el.scrollIntoView({
block: "center",
});
window.addEventListener("wheel", removeHash);
}, [hash, removeHash]);
React.useEffect(() => {
const url = new URL(window.location.toString());
const sortKeyFromUrl = url.searchParams.get("sort");
if (sortKeyFromUrl) {
_setSortKey(sortKeyFromUrl);
}
const filterKeyFromUrl = url.searchParams.get("filter");
if (filterKeyFromUrl) {
_setKeyword(filterKeyFromUrl);
}
// hacky but it works for now
setTimeout(() => {
// don't scroll into view if user got here with back button
scrollIntoView();
}, 100);
}, [setSortKey, setKeyword, scrollIntoView]);
const images: GalleryImage[] = React.useMemo(() => {
const sort =
sortKey === "date"
? R.sort((node1: typeof data["allFile"]["nodes"][number], node2) => {
const date1 = new Date(
R.pathOr("", ["fields", "imageMeta", "dateTaken"], node1)
);
const date2 = new Date(
R.pathOr("", ["fields", "imageMeta", "dateTaken"], node2)
);
return -1 * (date1.getTime() - date2.getTime());
})
: R.sort(
// @ts-ignore
R.descend(R.path<GalleryImage>(SORT_KEYS[sortKey]))
);
const filter = filterKeyword
? R.filter((image) =>
R.includes(
filterKeyword,
R.pathOr([], ["fields", "imageMeta", "meta", "Keywords"], image)
)
)
: R.identity;
try {
const ret = R.pipe(
// @ts-ignore
sort,
filter
)(data.allFile.nodes) as any;
return ret;
} catch (e) {
console.log("caught images!", e);
return [];
}
}, [data, sortKey, filterKeyword]);
return (
<>
{/* @ts-ignore */}
<Helmet>
<title>Photo Gallery | Chuck Dries</title>
<body
className="bg-white transition-color"
// @ts-ignore
style={getHelmetSafeBodyStyle({
Muted: [0, 0, 0],
LightMuted: [0, 0, 0],
Vibrant: [0, 0, 0],
LightVibrant: [0, 0, 0],
DarkMuted: [238, 238, 238],
DarkVibrant: [238, 238, 238],
})}
/>
</Helmet>
<div className="top-0 z-10">
<div className="bg-vibrant-dark text-light-vibrant pb-1">
<Nav
className="mb-4"
internalLinks={[
{ href: "/", label: "Home" },
{ href: "/photogallery/", label: "Gallery" },
]}
/>
</div>
<div className="flex flex-col md:flex-row md:items-end justify-between px-4 md:px-8 sm:mx-auto">
<KeywordsPicker
keywords={[
"Boyce Thompson Arboretum",
"winter",
"night",
"coast",
// "city",
"landscape",
"flowers",
"product",
"waterfall",
"fireworks",
"panoramic",
"Portland Japanese Garden",
// "shoot the light",
// "sunset",
]}
onChange={setKeyword}
value={filterKeyword}
/>
<div className="m-2">
<Select
label="Sort by..."
// @ts-ignore
onSelectionChange={setSortKey}
selectedKey={sortKey}
>
<Item key="rating">Curated</Item>
<Item key="date">Date</Item>
<Item key="hue">Hue</Item>
</Select>
</div>
</div>
</div>
<MasonryGallery
aspectsByBreakpoint={{
xs: 2,
sm: 2,
md: 3,
lg: 4,
xl: 5,
"2xl": 6.1,
"3xl": 8,
}}
debugHue={sortKey === "hue_debug"}
debugRating={sortKey === "rating" && showDebug}
images={images}
linkState={{
sortKey,
filterKeyword,
}}
/>
</>
);
};
export const query = graphql`query GalleryPageQuery {
allFile(
filter: {sourceInstanceName: {eq: "gallery"}}
sort: {fields: {imageMeta: {dateTaken: DESC}}}
) {
nodes {
relativePath
base
childImageSharp {
fluid {
aspectRatio
}
gatsbyImageData(layout: CONSTRAINED, height: 550, placeholder: DOMINANT_COLOR)
}
fields {
imageMeta {
vibrantHue
dominantHue
dateTaken
meta {
Keywords
Rating
ObjectName
}
vibrant {
Vibrant
}
}
}
}
}
}`;
export default GalleryPage;