wip bring back homepage
This commit is contained in:
parent
bc3249c061
commit
4eec1ed28b
BIN
data/gallery/DSC02615-2.jpg
(Stored with Git LFS)
Normal file
BIN
data/gallery/DSC02615-2.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -62,6 +62,7 @@
|
|||||||
"ramda": "^0.27.1",
|
"ramda": "^0.27.1",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-cool-dimensions": "^2.0.7",
|
"react-cool-dimensions": "^2.0.7",
|
||||||
|
"react-div-100vh": "^0.7.0",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"sass": "^1.34.0",
|
"sass": "^1.34.0",
|
||||||
|
@ -13,7 +13,7 @@ const ExternalLinks = ({ isVertical }: { isVertical: boolean }) => (
|
|||||||
className={classnames(
|
className={classnames(
|
||||||
"z-30 bg-vibrant-dark rounded-xl overflow-hidden",
|
"z-30 bg-vibrant-dark rounded-xl overflow-hidden",
|
||||||
isVertical
|
isVertical
|
||||||
? "inline-flex flex-wrap justify-center"
|
? "inline-flex flex-wrap justify-center bg-vibrant-dark rounded-xl"
|
||||||
: "absolute rounded-md top-[40px] border border-vibrant-light"
|
: "absolute rounded-md top-[40px] border border-vibrant-light"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
4529
src/gatsby-types.d.ts
vendored
4529
src/gatsby-types.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -1,251 +1,288 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as R from "ramda";
|
|
||||||
import { graphql, PageProps } from "gatsby";
|
import { graphql, PageProps } from "gatsby";
|
||||||
|
import { GatsbyImage, getImage } from "gatsby-plugin-image";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { Picker, Item } from "@adobe/react-spectrum";
|
import { take } from "ramda";
|
||||||
|
import classnames from "classnames";
|
||||||
|
|
||||||
import MasonryGallery from "../components/MasonryGallery";
|
import { getHelmetSafeBodyStyle, getVibrant, getAspectRatio } from "../utils";
|
||||||
import KeywordsPicker from "../components/KeywordsPicker";
|
|
||||||
import { getGalleryPageUrl, getHelmetSafeBodyStyle } from "../utils";
|
|
||||||
import Nav from "../components/Nav";
|
import Nav from "../components/Nav";
|
||||||
|
import ActionButtons from "../components/index/ActionButtons";
|
||||||
|
import { use100vh } from "react-div-100vh";
|
||||||
|
import { useMediaQuery } from "../useMediaQuery";
|
||||||
|
|
||||||
const SORT_KEYS = {
|
const env =
|
||||||
hue: ["fields", "imageMeta", "vibrantHue"],
|
process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || "development";
|
||||||
rating: ["fields", "imageMeta", "meta", "Rating"],
|
|
||||||
hue_debug: ["fields", "imageMeta", "dominantHue", 0],
|
|
||||||
date: [],
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type GalleryImage =
|
export type HomepageImage = Queries.IndexPageQuery["allFile"]["nodes"][number];
|
||||||
Queries.GalleryPageQueryQuery["allFile"]["nodes"][number];
|
|
||||||
|
|
||||||
const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
const getDifferentRand = (
|
||||||
const hash =
|
range: number,
|
||||||
typeof window !== "undefined" ? window.location.hash.replace("#", "") : "";
|
lastNs: number[],
|
||||||
|
iterations = 0
|
||||||
|
): number => {
|
||||||
|
const n = Math.floor(Math.random() * range);
|
||||||
|
if (lastNs.findIndex((x) => x === n) > -1 && iterations < 5) {
|
||||||
|
console.log("got dupe, trying again", n);
|
||||||
|
return getDifferentRand(range, lastNs, iterations + 1);
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
const [hashCleared, setHashCleared] = React.useState(false); // eslint-disable-line no-unused-vars
|
const IndexPage = ({
|
||||||
// ^ used just to force a re-render with the cleared hash value (I know, it's a smell for sure)
|
data: {
|
||||||
const [filterKeyword, _setKeyword] = React.useState(null as string | null);
|
allFile: { nodes: images },
|
||||||
const [sortKey, _setSortKey] = React.useState("rating" as string);
|
},
|
||||||
const showDebug =
|
}: PageProps<Queries.IndexPageQuery>) => {
|
||||||
typeof window !== "undefined" &&
|
const [isClient, setIsClient] = React.useState(false);
|
||||||
window.location.search.includes("debug=true");
|
const [imageIndex, setImageIndex] = React.useState(0);
|
||||||
|
const image = React.useMemo(() => {
|
||||||
|
return images[imageIndex];
|
||||||
|
}, [images, imageIndex]);
|
||||||
|
|
||||||
const setKeyword = React.useCallback(
|
const shuffleImage = React.useCallback(
|
||||||
(newKeyword: string | null) => {
|
(currentImage?: typeof images[number]) => {
|
||||||
if (newKeyword) {
|
const lastThreeImages =
|
||||||
|
JSON.parse(localStorage.getItem("lastHeros") ?? "[]") || [];
|
||||||
|
if (env === "production") {
|
||||||
try {
|
try {
|
||||||
window.plausible("Filter Keyword", {
|
window.plausible("Shuffle", {
|
||||||
props: { keyword: newKeyword },
|
props: { currentImage: currentImage?.base },
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// do nothing
|
/* do nothing */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_setKeyword(newKeyword);
|
const index = getDifferentRand(images.length, lastThreeImages);
|
||||||
window.history.replaceState(
|
localStorage.setItem(
|
||||||
null,
|
"lastHeros",
|
||||||
"",
|
JSON.stringify(take(3, [index, ...lastThreeImages]))
|
||||||
getGalleryPageUrl({ keyword: newKeyword, sortKey }, hash)
|
|
||||||
);
|
);
|
||||||
|
setImageIndex(index);
|
||||||
},
|
},
|
||||||
[_setKeyword, sortKey, hash]
|
[images.length]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setSortKey = React.useCallback(
|
// pick random image on page hydration
|
||||||
(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(() => {
|
React.useEffect(() => {
|
||||||
const url = new URL(window.location.toString());
|
if (!isClient) {
|
||||||
|
setIsClient(true);
|
||||||
const sortKeyFromUrl = url.searchParams.get("sort");
|
shuffleImage(image);
|
||||||
if (sortKeyFromUrl) {
|
|
||||||
_setSortKey(sortKeyFromUrl);
|
|
||||||
}
|
}
|
||||||
|
}, [isClient, imageIndex, image, shuffleImage]);
|
||||||
|
|
||||||
const filterKeyFromUrl = url.searchParams.get("filter");
|
// React.useEffect(() => {
|
||||||
if (filterKeyFromUrl) {
|
// const keyListener = (e: KeyboardEvent) => {
|
||||||
_setKeyword(filterKeyFromUrl);
|
// switch (e.code) {
|
||||||
}
|
// case "Space": {
|
||||||
|
// shuffleImage(image);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// case "ArrowRight": {
|
||||||
|
// if (imageIndex === images.length - 1) {
|
||||||
|
// setImageIndex(0);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// setImageIndex(imageIndex + 1);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
// hacky but it works for now
|
// case "ArrowLeft": {
|
||||||
setTimeout(() => {
|
// if (imageIndex === 0) {
|
||||||
// don't scroll into view if user got here with back button
|
// setImageIndex(images.length - 1);
|
||||||
scrollIntoView();
|
// return;
|
||||||
}, 100);
|
// }
|
||||||
}, [setSortKey, setKeyword, scrollIntoView]);
|
// setImageIndex(imageIndex - 1);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// document.addEventListener("keydown", keyListener);
|
||||||
|
// return () => {
|
||||||
|
// document.removeEventListener("keydown", keyListener);
|
||||||
|
// };
|
||||||
|
// }, [imageIndex, images.length, image, shuffleImage]);
|
||||||
|
|
||||||
const images: GalleryImage[] = React.useMemo(() => {
|
const browserIsLandscape = useMediaQuery("(orientation: landscape)");
|
||||||
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
|
const vibrant = getVibrant(image);
|
||||||
? R.filter((image) =>
|
const ar = getAspectRatio(image);
|
||||||
R.includes(
|
|
||||||
filterKeyword,
|
|
||||||
R.pathOr([], ["fields", "imageMeta", "meta", "Keywords"], image)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: R.identity;
|
|
||||||
|
|
||||||
try {
|
const screenHeight = use100vh();
|
||||||
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]);
|
|
||||||
|
|
||||||
|
const imageIsLandscape = isClient ? ar > 1 : true;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const img = getImage(image);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>Photo Gallery | Chuck Dries</title>
|
<title>Chuck Dries</title>
|
||||||
<body
|
<body
|
||||||
className="bg-black text-white"
|
className={classnames(isClient ? "bg-muted-dark" : "bg-gray-800")}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
style={getHelmetSafeBodyStyle({
|
style={getHelmetSafeBodyStyle(vibrant!, screenHeight)}
|
||||||
Muted: [255, 255, 255],
|
|
||||||
DarkMuted: [0, 0, 0],
|
|
||||||
LightMuted: [255, 255, 255],
|
|
||||||
Vibrant: [255, 255, 255],
|
|
||||||
LightVibrant: [231, 229, 228],
|
|
||||||
DarkVibrant: [0, 0, 0],
|
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="top-0 z-10">
|
<main
|
||||||
<div className="bg-vibrant-dark text-light-vibrant pb-1">
|
className={classnames(
|
||||||
|
"font-serif",
|
||||||
|
imageIsLandscape
|
||||||
|
? "landscape:grid portrait:h-actual-screen portrait:flex flex-col justify-around"
|
||||||
|
: "portrait:grid landscape:flex flex-row"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
"landscape:flex-auto flex flex-col items-center",
|
||||||
|
imageIsLandscape
|
||||||
|
? "portrait:items-center landscape:w-screen justify-between"
|
||||||
|
: "landscape:justify-center portrait:w-screen"
|
||||||
|
)}
|
||||||
|
style={{ gridArea: "1/1" }}
|
||||||
|
>
|
||||||
<Nav
|
<Nav
|
||||||
className="mb-4"
|
internalLinks={[
|
||||||
internalLinks={[{ href: "/", label: "Home" }]}
|
{ href: "/", label: "Home" },
|
||||||
/>
|
{ href: "/photogallery/", label: "Gallery" },
|
||||||
<div className="flex flex-col text-center mr-5 md:ml-4 ml-2 my-4 md:my-7 font-serif">
|
|
||||||
<h1 className="text-5xl mt-0 font-black z-10">Chuck Dries</h1>
|
|
||||||
<h2 className="">Full Stack Software Engineer & Photographer</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col md:flex-row md:items-end justify-between sm:container sm:mx-auto">
|
|
||||||
<KeywordsPicker
|
|
||||||
keywords={[
|
|
||||||
"night",
|
|
||||||
"coast",
|
|
||||||
// "city",
|
|
||||||
"landscape",
|
|
||||||
"flowers",
|
|
||||||
"product",
|
|
||||||
"waterfall",
|
|
||||||
"fireworks",
|
|
||||||
"panoramic",
|
|
||||||
"Portland Japanese Garden",
|
|
||||||
"shoot the light",
|
|
||||||
// "sunset",
|
|
||||||
]}
|
]}
|
||||||
onChange={setKeyword}
|
|
||||||
value={filterKeyword}
|
|
||||||
/>
|
/>
|
||||||
<div className="m-2">
|
<div
|
||||||
<Picker
|
className={classnames(
|
||||||
label="Sort by..."
|
"flex flex-col",
|
||||||
// @ts-ignore
|
!imageIsLandscape && "portrait:flex-auto "
|
||||||
onSelectionChange={setSortKey}
|
)}
|
||||||
selectedKey={sortKey}
|
>
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
"rounded-[50px] p-3 md:p-5 ml-2 mr-4 md:ml-3 md:mr-5 flex flex-col items-center z-10 mb-3",
|
||||||
|
isClient
|
||||||
|
? imageIsLandscape
|
||||||
|
? "text-vibrant-light landscape:text-vibrant-dark landscape:cool-border-big landscape:border-r-[20px] landscape:border-b-[20px]"
|
||||||
|
: "text-vibrant-light portrait:text-vibrant-dark portrait:cool-border-big portrait:border-r-[20px] portrait:border-b-[20px]"
|
||||||
|
: "bg-gray-50 border-r-[20px] border-b-[20px]",
|
||||||
|
isClient && ""
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Item key="rating">Curated</Item>
|
<h1
|
||||||
<Item key="date">Date</Item>
|
className={classnames(
|
||||||
<Item key="hue">Hue</Item>
|
"mb-0 mt-0 text-center font-black z-20 text-5xl sm:text-7xl md:text-8xl lg:text-9xl"
|
||||||
</Picker>
|
)}
|
||||||
|
style={{ lineHeight: "85%" }}
|
||||||
|
>
|
||||||
|
Chuck Dries
|
||||||
|
</h1>
|
||||||
|
<h2
|
||||||
|
className={classnames(
|
||||||
|
"p-3 text-center z-20 font-bold text-lg md:text-2xl lg:text-3xl"
|
||||||
|
)}
|
||||||
|
style={{ lineHeight: "110%" }}
|
||||||
|
>
|
||||||
|
Full Stack Software Engineer & Photographer
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
imageIsLandscape ? "block portrait:hidden" : ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ActionButtons
|
||||||
|
image={image}
|
||||||
|
isClient={isClient}
|
||||||
|
shuffleImage={shuffleImage}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{isClient && img ? (
|
||||||
<MasonryGallery
|
<GatsbyImage
|
||||||
aspectsByBreakpoint={{
|
alt=""
|
||||||
xs: 2,
|
className={classnames(
|
||||||
sm: 2,
|
imageIsLandscape
|
||||||
md: 3,
|
? "landscape:h-actual-screen portrait:h-two-thirds-vw"
|
||||||
lg: 4,
|
: "h-actual-screen portrait:w-full"
|
||||||
xl: 5,
|
)}
|
||||||
"2xl": 6.1,
|
image={img}
|
||||||
"3xl": 8,
|
loading="eager"
|
||||||
}}
|
style={{
|
||||||
debugHue={sortKey === "hue_debug"}
|
gridArea: "1/1",
|
||||||
debugRating={sortKey === "rating" && showDebug}
|
width:
|
||||||
images={images}
|
browserIsLandscape && !imageIsLandscape
|
||||||
linkState={{
|
? `${ar * 100}vh`
|
||||||
sortKey,
|
: "100%",
|
||||||
filterKeyword,
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
) : (
|
||||||
|
<div
|
||||||
|
className="landscape:h-actual-screen portrait:h-two-thirds-vw w-full"
|
||||||
|
style={{ gridArea: "1/1" }}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
{imageIsLandscape && (
|
||||||
|
<div className="hidden portrait:flex justify-center sm:my-2">
|
||||||
|
<ActionButtons
|
||||||
|
image={image}
|
||||||
|
isClient={isClient}
|
||||||
|
shuffleImage={shuffleImage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const query = graphql`
|
export const query = graphql`
|
||||||
query GalleryPageQuery {
|
query IndexPage {
|
||||||
allFile(
|
allFile(
|
||||||
filter: { sourceInstanceName: { eq: "gallery" } }
|
filter: {
|
||||||
sort: { fields: fields___imageMeta___dateTaken, order: DESC }
|
sourceInstanceName: { eq: "gallery" }
|
||||||
|
base: {
|
||||||
|
in: [
|
||||||
|
# "DSC06616.jpg" # B&W abstract ## KEEP ON TOP
|
||||||
|
# "20160530-DSC09108.jpg" # portrait red flowers
|
||||||
|
# # "DSC00201.jpg" # duck
|
||||||
|
# "DSC04905.jpg" # purple layers
|
||||||
|
# "DSC05761.jpg" # monument valley
|
||||||
|
# "DSC05851.jpg" # utahn highway sunset
|
||||||
|
# # "DSC06245.jpg" # snowy milky way
|
||||||
|
# # # "DSC08521.jpg" # firepit bloom j&e
|
||||||
|
# # "DSC07490.jpg" # house on prairie
|
||||||
|
# # "DSC02538.jpg" # portrait pink cactus bloom
|
||||||
|
# # "20190624-DSC00771.jpg" # glacier forest fog
|
||||||
|
# # # "DSC00237.jpg" # cotton candy clouds
|
||||||
|
# "_DSC6062.jpg" # field of wildflowers
|
||||||
|
# # "_DSC6060.jpg" # edge of the world
|
||||||
|
# "_DSC6219.jpg" # whihte/yellow rosebush
|
||||||
|
# # "_DSC6243.jpg" # bright rose in darkness
|
||||||
|
# # "_DSC6400-2.jpg" # Horsetail falls
|
||||||
|
# # "_DSC6798.jpg" # Japanese zen garden
|
||||||
|
# # "_DSC6481.jpg" # Mt Hood from Powell Butte
|
||||||
|
# # "_DSC5916.jpg" # blue dart stinger
|
||||||
|
# # "_DSC0286.jpg" # god rays
|
||||||
|
# # "_DSC8998.jpg" # forest road
|
||||||
|
# # "DSC01169.jpg" # ferris wheel reflection
|
||||||
|
# "DSC01800.jpg" # cherry blossom landscape sunny sky
|
||||||
|
# "DSC01772.jpg" # cherry blossom portrait sunny sky
|
||||||
|
# # "DSC06201.jpg" # Wheatland snowy hills
|
||||||
|
# "DSC01924.jpg" # cherry blossom sea
|
||||||
|
# # "DSC03157.jpg" # constellation of flowers
|
||||||
|
# "DSC02610.jpg" # peter iredale portrait
|
||||||
|
# "DSC02615.jpg" # rori iredale beach field camera
|
||||||
|
"DSC02615-2.jpg" # same but red
|
||||||
|
# "DSC06490.jpg" # Japanese garden steps
|
||||||
|
# "DSC06687.jpg" # Multnomah Falls long exposure
|
||||||
|
# "DSC09932.jpg" # milky way
|
||||||
|
# "DSC09944.jpg" # milky way rori
|
||||||
|
# "DSC03725.jpg" # oregon coast lighthouse
|
||||||
|
# "DSC03750.jpg"
|
||||||
|
# "DSC03804.jpg"
|
||||||
|
# "DSC04122.jpg" # shoot the light wheel hallway
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort: { order: DESC, fields: fields___imageMeta___dateTaken }
|
||||||
) {
|
) {
|
||||||
nodes {
|
nodes {
|
||||||
relativePath
|
relativePath
|
||||||
@ -255,23 +292,15 @@ export const query = graphql`
|
|||||||
aspectRatio
|
aspectRatio
|
||||||
}
|
}
|
||||||
gatsbyImageData(
|
gatsbyImageData(
|
||||||
layout: CONSTRAINED
|
layout: FULL_WIDTH
|
||||||
height: 550
|
placeholder: NONE
|
||||||
placeholder: DOMINANT_COLOR
|
breakpoints: [750, 1080, 1366, 1920, 2560, 3840]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fields {
|
fields {
|
||||||
imageMeta {
|
imageMeta {
|
||||||
vibrantHue
|
|
||||||
dominantHue
|
|
||||||
dateTaken
|
|
||||||
meta {
|
|
||||||
Keywords
|
|
||||||
Rating
|
|
||||||
ObjectName
|
|
||||||
}
|
|
||||||
vibrant {
|
vibrant {
|
||||||
Vibrant
|
...VibrantColors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,4 +309,4 @@ export const query = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default GalleryPage;
|
export default IndexPage;
|
||||||
|
@ -1,10 +1,283 @@
|
|||||||
import { navigate } from 'gatsby';
|
import * as React from "react";
|
||||||
import React, { useEffect } from 'react';
|
import * as R from "ramda";
|
||||||
|
import { graphql, PageProps } from "gatsby";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { Picker, Item } from "@adobe/react-spectrum";
|
||||||
|
|
||||||
export default function RedirectGallery() {
|
import MasonryGallery from "../components/MasonryGallery";
|
||||||
useEffect(() => {
|
import KeywordsPicker from "../components/KeywordsPicker";
|
||||||
navigate('/');
|
import { getGalleryPageUrl, getHelmetSafeBodyStyle } from "../utils";
|
||||||
|
import Nav from "../components/Nav";
|
||||||
|
|
||||||
|
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);
|
||||||
}, []);
|
}, []);
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
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-black text-white"
|
||||||
|
// @ts-ignore
|
||||||
|
style={getHelmetSafeBodyStyle({
|
||||||
|
Muted: [255, 255, 255],
|
||||||
|
DarkMuted: [0, 0, 0],
|
||||||
|
LightMuted: [255, 255, 255],
|
||||||
|
Vibrant: [255, 255, 255],
|
||||||
|
LightVibrant: [231, 229, 228],
|
||||||
|
DarkVibrant: [0, 0, 0],
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</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" }]}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col text-center mr-5 md:ml-4 ml-2 my-4 md:my-7 font-serif">
|
||||||
|
<h1 className="text-5xl mt-0 font-black z-10">Chuck Dries</h1>
|
||||||
|
<h2 className="">Full Stack Software Engineer & Photographer</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col md:flex-row md:items-end justify-between sm:container sm:mx-auto">
|
||||||
|
<KeywordsPicker
|
||||||
|
keywords={[
|
||||||
|
"night",
|
||||||
|
"coast",
|
||||||
|
// "city",
|
||||||
|
"landscape",
|
||||||
|
"flowers",
|
||||||
|
"product",
|
||||||
|
"waterfall",
|
||||||
|
"fireworks",
|
||||||
|
"panoramic",
|
||||||
|
"Portland Japanese Garden",
|
||||||
|
"shoot the light",
|
||||||
|
// "sunset",
|
||||||
|
]}
|
||||||
|
onChange={setKeyword}
|
||||||
|
value={filterKeyword}
|
||||||
|
/>
|
||||||
|
<div className="m-2">
|
||||||
|
<Picker
|
||||||
|
label="Sort by..."
|
||||||
|
// @ts-ignore
|
||||||
|
onSelectionChange={setSortKey}
|
||||||
|
selectedKey={sortKey}
|
||||||
|
>
|
||||||
|
<Item key="rating">Curated</Item>
|
||||||
|
<Item key="date">Date</Item>
|
||||||
|
<Item key="hue">Hue</Item>
|
||||||
|
</Picker>
|
||||||
|
</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: fields___imageMeta___dateTaken, order: 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;
|
||||||
|
@ -11914,6 +11914,11 @@ react-dev-utils@^12.0.1:
|
|||||||
strip-ansi "^6.0.1"
|
strip-ansi "^6.0.1"
|
||||||
text-table "^0.2.0"
|
text-table "^0.2.0"
|
||||||
|
|
||||||
|
react-div-100vh@^0.7.0:
|
||||||
|
version "0.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-div-100vh/-/react-div-100vh-0.7.0.tgz#b3bec03a833fa40e406f36ed2e23a35a59d1068f"
|
||||||
|
integrity sha512-I3d77tQyaSlOx/6vurDDLk7upb5GA2ldEtVXkk7Kn5cy+tLlS0KlqDK14xUxlxh7jz4StjgKcwFyrpymsPpomA==
|
||||||
|
|
||||||
react-dom@^17.0.1:
|
react-dom@^17.0.1:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz"
|
resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user