Personal-Website/src/pages/photogallery.tsx

388 lines
10 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as React from "react";
import * as R from "ramda";
import { graphql, Link, navigate, 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 {
compareDates,
getGalleryPageUrl,
getHelmetSafeBodyStyle,
getVibrantStyle,
} from "../utils";
import Nav from "../components/Nav";
import { Item, Select } from "../components/Select";
import { Switch } from "../components/Switch";
import ColorPalette from "@spectrum-icons/workflow/ColorPalette";
const SORT_KEYS = {
hue: ["fields", "imageMeta", "vibrantHue"],
rating: ["fields", "imageMeta", "meta", "Rating"],
// hue_debug: ["fields", "imageMeta", "dominantHue", 0],
hue_debug: ["fields", "imageMeta", "dominantHue", "0"],
date: ["fields", "imageMeta", "dateTaken"],
datePublished: ["fields", "imageMeta", "datePublished"],
} as const;
export type GalleryImage =
Queries.GalleryPageQueryQuery["all"]["nodes"][number];
function smartCompareDates(
key: keyof typeof SORT_KEYS,
left: GalleryImage,
right: GalleryImage
) {
let diff = compareDates(SORT_KEYS[key], left, right);
if (diff !== 0) {
return diff;
}
return compareDates(SORT_KEYS.date, left, right);
}
const GalleryPage = ({
data,
location,
}: PageProps<Queries.GalleryPageQueryQuery>) => {
const hash = location.hash ? location.hash.replace("#", "") : "";
const params = new URLSearchParams(location.search);
const filterKeyword = params.get("filter");
const sortKey = params.get("sort") ?? "rating";
const showDebug = Boolean(params.get("debug")?.length);
const [showPalette, setShowPalette] = React.useState(false);
const onKeywordPick = React.useCallback((newKeyword: string | null) => {
if (newKeyword) {
try {
window.plausible("Filter Keyword", {
props: { keyword: newKeyword },
});
} catch (e) {
// do nothing
}
}
}, []);
const setSortKey = React.useCallback(
(newSortKey: string) => {
try {
window.plausible("Sort Gallery", {
props: { key: newSortKey },
});
} catch (e) {
// do nothing
}
navigate(
getGalleryPageUrl(
{ sortKey: newSortKey, keyword: filterKeyword, showDebug },
hash
),
{ replace: true }
);
},
[filterKeyword, hash, showDebug]
);
const removeHash = React.useCallback(() => {
if (!hash.length) {
return;
}
// const url = new URL(
// typeof window !== "undefined"
// ? window.location.href.toString()
// : "https://chuckdries.com/photogallery/"
// );
// url.hash = "";
// window.history.replaceState(null, "", url.href.toString());
navigate(getGalleryPageUrl({ sortKey, keyword: filterKeyword, showDebug}, ""), { replace: true })
window.removeEventListener("wheel", removeHash);
window.removeEventListener("touchmove", removeHash);
}, [hash, sortKey, filterKeyword, showDebug]);
React.useEffect(() => {
window.addEventListener("wheel", removeHash);
window.addEventListener("touchmove", removeHash);
return () => {
window.removeEventListener("wheel", removeHash);
window.removeEventListener("touchmove", removeHash);
}
}, [removeHash]);
React.useEffect(() => {
// hacky but it works for now
requestAnimationFrame(() => {
// don't scroll into view if user got here with back button or if we just cleared it
if (!hash.length) {
return;
}
const el = document.getElementById(hash);
if (!el) {
console.log("⚠failed to find hash");
return;
}
console.log("scrolling into view manually");
el.scrollIntoView({
block: hash.startsWith("all") ? "start" : "center",
});
});
}, [hash]);
const images: GalleryImage[] = React.useMemo(() => {
const sort =
sortKey === "date" || sortKey === "datePublished"
? R.sort((node1: typeof data["all"]["nodes"][number], node2) =>
smartCompareDates(sortKey, node1, node2)
)
: 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.all.nodes) as any;
return ret;
} catch (e) {
console.log("caught images!", e);
return [];
}
}, [data, sortKey, filterKeyword]);
const recents = React.useMemo(() => {
return R.sort(
(left, right) => smartCompareDates("datePublished", left, right),
data.recents.nodes
);
}, [data]);
const dataFn = React.useCallback(
(image: GalleryImage): string | null => {
if (!showDebug) {
return null;
}
if (sortKey === "rating") {
return `[${R.pathOr(null, SORT_KEYS.rating, image)}] ${image.base}`;
}
if (sortKey === "datePublished") {
const date = R.pathOr(null, SORT_KEYS.datePublished, image);
if (!date) {
return null;
}
return new Date(date).toLocaleString();
}
return null;
},
[showDebug, sortKey]
);
return (
<>
{/* @ts-ignore */}
<Helmet>
<title>Photo Gallery | Chuck Dries</title>
<body
className="bg-white transition-color"
// @ts-ignore
style={getHelmetSafeBodyStyle(
// @ts-ignore shrug
getVibrantStyle({
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="gradient pb-6">
<div className="px-4 md:px-8 flex items-baseline">
<h3 className="mx-2 font-bold" id="recently">
Recently published
</h3>
{sortKey !== "datePublished" && (
<Link
className="underline cursor-pointer text-gray-500"
to="?sort=datePublished#all"
>
show more
</Link>
)}
</div>
<MasonryGallery
aspectsByBreakpoint={{
xs: 3,
sm: 3,
md: 4,
lg: 4,
xl: 5,
"2xl": 6,
"3xl": 8,
}}
images={recents}
singleRow
/>
</div>
<div className="px-4 md:px-8 mt-2 pt-2">
<h3 className="mx-2 font-bold" id="all">
All images
</h3>
</div>
<div className="flex flex-col lg:flex-row lg:items-end justify-between px-4 md:px-8 sm:mx-auto">
<KeywordsPicker
getHref={(val, selected) =>
selected
? getGalleryPageUrl({ keyword: null, sortKey, showDebug }, hash)
: getGalleryPageUrl({ keyword: val, sortKey, showDebug }, hash)
}
keywords={[
"Boyce Thompson Arboretum",
"winter",
"night",
"coast",
// "city",
"landscape",
"flowers",
"product",
// "waterfall",
// "fireworks",
// "panoramic",
"Portland Japanese Garden",
// "shoot the light",
// "sunset",
]}
onPick={onKeywordPick}
value={filterKeyword}
/>
<div className="my-2 mr-2 flex flex-row items-end">
<div className="border border-gray-400 rounded mr-2">
<Switch
isSelected={showPalette}
onChange={(val) => setShowPalette(val)}
>
<ColorPalette
UNSAFE_style={{
width: "24px",
margin: "0 4px",
}}
/>
</Switch>
</div>
<Select
label="Sort by..."
// @ts-ignore
onSelectionChange={setSortKey}
selectedKey={sortKey}
>
<Item key="rating">Curated</Item>
<Item key="datePublished">Date published</Item>
<Item key="date">Date taken</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,
}}
dataFn={dataFn}
debugHue={sortKey === "hue_debug"}
images={images}
linkState={{
sortKey,
filterKeyword,
}}
showPalette={showPalette}
/>
</>
);
};
export const query = graphql`
query GalleryPageQuery {
recents: allFile(
filter: { sourceInstanceName: { eq: "gallery" } }
sort: { fields: { imageMeta: { datePublished: DESC } } }
limit: 10
) {
...GalleryImageFile
}
all: allFile(
filter: { sourceInstanceName: { eq: "gallery" } }
sort: { fields: { imageMeta: { dateTaken: DESC } } }
) {
...GalleryImageFile
}
}
fragment GalleryImageFile on FileConnection {
nodes {
base
childImageSharp {
fluid {
aspectRatio
}
gatsbyImageData(
layout: CONSTRAINED
height: 550
placeholder: DOMINANT_COLOR
)
}
fields {
imageMeta {
vibrantHue
dominantHue
dateTaken
datePublished
meta {
Keywords
Rating
ObjectName
CreateDate
ModifyDate
}
vibrant {
...VibrantColors
}
}
}
}
}
`;
export default GalleryPage;