Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 534ee35451 | |||
| fa50031f0a | |||
| 1f9fa7b204 | |||
| 496185cc88 | |||
| e046649be1 | |||
| 6214944649 |
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -14,25 +14,6 @@ export const onRouteUpdate = function () {
|
|||||||
window.plausible("pageview");
|
window.plausible("pageview");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// docs say you can return a scroll position from this fn, but that's a bold-faced lie
|
|
||||||
export const shouldUpdateScroll = ({
|
|
||||||
prevRouterProps,
|
|
||||||
routerProps: { location },
|
|
||||||
pathname,
|
|
||||||
}) => {
|
|
||||||
if (pathname.startsWith('/photogallery/') && pathname !== '/photogallery/' ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (prevRouterProps?.location.pathname === pathname) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pathname === "/photogallery/" && location.hash.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// import * as React from 'react';
|
// import * as React from 'react';
|
||||||
// import { MDXProvider } from '@mdx-js/react';
|
// import { MDXProvider } from '@mdx-js/react';
|
||||||
|
|
||||||
|
|||||||
+8
-25
@@ -10,9 +10,9 @@ import sharp from "sharp";
|
|||||||
import { Palette } from "node-vibrant/lib/color";
|
import { Palette } from "node-vibrant/lib/color";
|
||||||
import { performance } from "perf_hooks";
|
import { performance } from "perf_hooks";
|
||||||
|
|
||||||
import util from "node:util";
|
import util from 'node:util';
|
||||||
import { exec as _exec } from "child_process";
|
import { exec as _exec } from "child_process";
|
||||||
const exec = util.promisify(_exec);
|
const exec = util.promisify(_exec)
|
||||||
|
|
||||||
// const path = require("path");
|
// const path = require("path");
|
||||||
// const Vibrant = require("node-vibrant");
|
// const Vibrant = require("node-vibrant");
|
||||||
@@ -119,7 +119,7 @@ function transformMetaToNodeData(
|
|||||||
vibrantData: Palette,
|
vibrantData: Palette,
|
||||||
imagePath: string,
|
imagePath: string,
|
||||||
{ r, g, b }: { r: number; b: number; g: number },
|
{ r, g, b }: { r: number; b: number; g: number },
|
||||||
datePublished: string
|
datePublished: string,
|
||||||
) {
|
) {
|
||||||
const vibrant = vibrantData ? processColors(vibrantData, imagePath) : null;
|
const vibrant = vibrantData ? processColors(vibrantData, imagePath) : null;
|
||||||
const vibrantHue = vibrantData.Vibrant!.getHsl()[0] * 360;
|
const vibrantHue = vibrantData.Vibrant!.getHsl()[0] * 360;
|
||||||
@@ -180,34 +180,18 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async function ({
|
|||||||
const { createNodeField } = actions;
|
const { createNodeField } = actions;
|
||||||
|
|
||||||
if (node.internal.type === "File" && node.sourceInstanceName === "gallery") {
|
if (node.internal.type === "File" && node.sourceInstanceName === "gallery") {
|
||||||
const { stdout: datePublished, stderr } = await exec(
|
const { stdout: datePublished, stderr } = await exec(`git log --diff-filter=A --follow --format=%aI -1 -- ${node.absolutePath}`)
|
||||||
`git log --diff-filter=A --follow --format=%aI -1 -- ${node.absolutePath}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (stderr.length) {
|
if (stderr.length) {
|
||||||
console.error("something went wrong checking publish date: ", stderr);
|
console.error('something went wrong checking publish date: ', stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
let metaData;
|
const metaData = await exifr.parse(node.absolutePath as string, {
|
||||||
try {
|
|
||||||
metaData = await exifr.parse(node.absolutePath as string, {
|
|
||||||
iptc: true,
|
iptc: true,
|
||||||
xmp: true,
|
xmp: true,
|
||||||
// icc: true
|
// icc: true
|
||||||
});
|
});
|
||||||
} catch (e) {
|
|
||||||
console.error(`something wen wrong with exifr on image ${node.base}`, e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sharpImage: sharp.Sharp;
|
const sharpImage = sharp(node.absolutePath as string);
|
||||||
|
|
||||||
try {
|
|
||||||
sharpImage = sharp(node.absolutePath as string);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`something wen wrong with sharp on image ${node.base}`, e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
const { dominant } = await sharpImage.stats();
|
const { dominant } = await sharpImage.stats();
|
||||||
const resizedImage = await sharpImage
|
const resizedImage = await sharpImage
|
||||||
.resize({
|
.resize({
|
||||||
@@ -229,8 +213,7 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async function ({
|
|||||||
vibrantData,
|
vibrantData,
|
||||||
node.absolutePath as string,
|
node.absolutePath as string,
|
||||||
dominant,
|
dominant,
|
||||||
// if datePublished is empty, image has not been committed to git yet and is thus brand new
|
datePublished
|
||||||
datePublished.length ? datePublished.replace("\n", "") : new Date().toISOString()
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
"gatsby-source-filesystem": "^5.0.0",
|
"gatsby-source-filesystem": "^5.0.0",
|
||||||
"gatsby-transformer-sharp": "^5.0.0",
|
"gatsby-transformer-sharp": "^5.0.0",
|
||||||
"kebab-case": "^1.0.1",
|
"kebab-case": "^1.0.1",
|
||||||
"lodash.debounce": "^4.0.8",
|
|
||||||
"node-iptc": "^1.0.5",
|
"node-iptc": "^1.0.5",
|
||||||
"node-vibrant": "3.1.6",
|
"node-vibrant": "3.1.6",
|
||||||
"postcss": "^8.4.19",
|
"postcss": "^8.4.19",
|
||||||
@@ -61,7 +60,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chroma-js": "^2.1.4",
|
"@types/chroma-js": "^2.1.4",
|
||||||
"@types/lodash.debounce": "^4.0.7",
|
|
||||||
"@types/node": "^18.8.3",
|
"@types/node": "^18.8.3",
|
||||||
"@types/ramda": "^0.28.15",
|
"@types/ramda": "^0.28.15",
|
||||||
"@types/react": "^18.0.21",
|
"@types/react": "^18.0.21",
|
||||||
|
|||||||
@@ -63,15 +63,6 @@ const GalleryImage = ({ data, location: { state } }) => {
|
|||||||
setIsClient(true);
|
setIsClient(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
window.scrollTo({
|
|
||||||
top: 180,
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
}, 50);
|
|
||||||
}, [image.base]);
|
|
||||||
|
|
||||||
const nextIndex =
|
const nextIndex =
|
||||||
sortedImageList && currentIndex < sortedImageList.length
|
sortedImageList && currentIndex < sortedImageList.length
|
||||||
? currentIndex + 1
|
? currentIndex + 1
|
||||||
@@ -144,7 +135,7 @@ const GalleryImage = ({ data, location: { state } }) => {
|
|||||||
locationString = location.join(", ");
|
locationString = location.join(", ");
|
||||||
}
|
}
|
||||||
const vibrant = getVibrant(image, true);
|
const vibrant = getVibrant(image, true);
|
||||||
const BLEND = "hsl";
|
const BLEND = 'hsl'
|
||||||
const darkAccent = chroma
|
const darkAccent = chroma
|
||||||
.mix(vibrant.Vibrant, "hsla(216, 0%, 90%, 1)", 0.6, BLEND)
|
.mix(vibrant.Vibrant, "hsla(216, 0%, 90%, 1)", 0.6, BLEND)
|
||||||
.hex();
|
.hex();
|
||||||
@@ -154,7 +145,7 @@ const GalleryImage = ({ data, location: { state } }) => {
|
|||||||
ar > 1
|
ar > 1
|
||||||
? "flex-col"
|
? "flex-col"
|
||||||
: "portrait:mx-auto landscape:mx-5 landscape:flex-row-reverse portrait:flex-col";
|
: "portrait:mx-auto landscape:mx-5 landscape:flex-row-reverse portrait:flex-col";
|
||||||
const verticalPad = ar > 1 ? "180px" : "20px";
|
const verticalPad = ar > 1 ? "250px" : "100px";
|
||||||
|
|
||||||
const shutterSpeed = React.useMemo(
|
const shutterSpeed = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -1,37 +1,44 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Link } from "gatsby";
|
import Checkmark from "@spectrum-icons/workflow/Checkmark";
|
||||||
|
|
||||||
interface KeywordsPickerProps {
|
interface KeywordsPickerProps {
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
value: string | null;
|
value: string | null;
|
||||||
getHref: (value: string | null, selected: boolean) => string;
|
onChange: (val: string | null) => void;
|
||||||
onPick: (value: string | null) => void;
|
|
||||||
}
|
}
|
||||||
const KeywordsPicker = ({ keywords, value, getHref, onPick }: KeywordsPickerProps) => {
|
const KeywordsPicker = ({ keywords, value, onChange }: KeywordsPickerProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="mx-2 mt-2">
|
<div className="mx-2 mt-2">
|
||||||
<span className="text-xs text-black">Collections</span>
|
<span className="text-xs text-black">
|
||||||
|
Collections
|
||||||
|
</span>
|
||||||
<ul className="flex gap-1 flex-wrap mt-1 mb-2">
|
<ul className="flex gap-1 flex-wrap mt-1 mb-2">
|
||||||
{keywords.map((keyword) => {
|
{keywords.map((keyword) => {
|
||||||
const selected = value === keyword;
|
const selected = value === keyword;
|
||||||
return (
|
return (
|
||||||
<li key={keyword}>
|
<li key={keyword}>
|
||||||
<Link
|
<button
|
||||||
className={classNames(
|
className={classNames(
|
||||||
`py-[5px] px-3 rounded-full text-sm block`,
|
`py-[5px] px-3 rounded-full text-sm`,
|
||||||
`text-black border border-gray-400`,
|
`text-black border border-black`,
|
||||||
selected
|
selected
|
||||||
? "bg-black/10 font-bold"
|
? "bg-transparentblack font-bold"
|
||||||
: `bg-white
|
: `bg-white
|
||||||
hover:bg-black/10`
|
hover:bg-transparentblack`
|
||||||
)}
|
)}
|
||||||
onClick={() => onPick(keyword)}
|
onClick={() => (selected ? onChange(null) : onChange(keyword))}
|
||||||
replace={false}
|
type="button"
|
||||||
to={getHref(keyword, selected)}
|
|
||||||
>
|
>
|
||||||
{keyword}{" "}
|
{keyword}{" "}
|
||||||
</Link>
|
{/* {selected && (
|
||||||
|
<Checkmark
|
||||||
|
UNSAFE_className="mx-1"
|
||||||
|
UNSAFE_style={{ width: "15px" }}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
)} */}
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ function Option({ item, state }: OptionProps) {
|
|||||||
<li
|
<li
|
||||||
{...optionProps}
|
{...optionProps}
|
||||||
className={`p-2 outline-none cursor-default flex items-center justify-between ${text} text-sm ${
|
className={`p-2 outline-none cursor-default flex items-center justify-between ${text} text-sm ${
|
||||||
isFocused ? "bg-black/10" : ""
|
isFocused ? "bg-transparentblack" : ""
|
||||||
} ${isSelected ? "font-bold" : ""}`}
|
} ${isSelected ? "font-bold" : ""}`}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ interface MasonryGalleryProps {
|
|||||||
[breakpoint: string]: number;
|
[breakpoint: string]: number;
|
||||||
};
|
};
|
||||||
debugHue?: boolean;
|
debugHue?: boolean;
|
||||||
dataFn?: (image: GalleryImage) => string[] | null;
|
debugRating?: boolean;
|
||||||
linkState?: object;
|
linkState?: object;
|
||||||
showPalette?: boolean;
|
showPalette?: boolean;
|
||||||
singleRow?: boolean;
|
singleRow?: boolean;
|
||||||
@@ -33,7 +33,7 @@ const MasonryGallery = ({
|
|||||||
images: _images,
|
images: _images,
|
||||||
aspectsByBreakpoint: aspectTargetsByBreakpoint,
|
aspectsByBreakpoint: aspectTargetsByBreakpoint,
|
||||||
debugHue,
|
debugHue,
|
||||||
dataFn,
|
debugRating,
|
||||||
linkState,
|
linkState,
|
||||||
showPalette,
|
showPalette,
|
||||||
singleRow,
|
singleRow,
|
||||||
@@ -47,8 +47,13 @@ const MasonryGallery = ({
|
|||||||
[aspectTargetsByBreakpoint]
|
[aspectTargetsByBreakpoint]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// const { observe, currentBreakpoint } = useDimensions({
|
||||||
|
// breakpoints,
|
||||||
|
// });
|
||||||
|
|
||||||
const { breakpoint } = useBreakpoint(breakpoints, "xs");
|
const { breakpoint } = useBreakpoint(breakpoints, "xs");
|
||||||
|
|
||||||
|
// const breakpoint = currentBreakpoint.length ? currentBreakpoint : "xs";
|
||||||
const galleryWidth = `calc(100vw - ${
|
const galleryWidth = `calc(100vw - ${
|
||||||
breakpoint === "xs" || breakpoint === "sm" ? "32" : "160"
|
breakpoint === "xs" || breakpoint === "sm" ? "32" : "160"
|
||||||
}px)`;
|
}px)`;
|
||||||
@@ -59,38 +64,48 @@ const MasonryGallery = ({
|
|||||||
) as number[];
|
) as number[];
|
||||||
|
|
||||||
const targetAspect = aspectTargetsByBreakpoint[breakpoint];
|
const targetAspect = aspectTargetsByBreakpoint[breakpoint];
|
||||||
const rows = React.useMemo(() => {
|
const rows = React.useMemo(
|
||||||
const _rows: Row[] = [{ aspect: 0, startIndex: 0, images: 0 }];
|
() =>
|
||||||
|
R.pipe(
|
||||||
for (const currentAspect of aspectRatios) {
|
R.reduce(
|
||||||
const currentRow = _rows[_rows.length - 1];
|
(acc, currentAspect: number): Row[] => {
|
||||||
|
const currentRow = acc.pop()!;
|
||||||
const currentDiff = Math.abs(targetAspect - currentRow.aspect);
|
const currentDiff = Math.abs(targetAspect - currentRow.aspect);
|
||||||
const diffIfImageIsAddedToCurrentRow = Math.abs(
|
const diffIfImageIsAddedToCurrentRow = Math.abs(
|
||||||
targetAspect - (currentRow.aspect + currentAspect)
|
targetAspect - (currentRow.aspect + currentAspect)
|
||||||
);
|
);
|
||||||
|
// add image to current row if it gets us closer to our target aspect ratio
|
||||||
// does adding current image to our row get us closer to our target aspect ratio?
|
|
||||||
if (currentDiff > diffIfImageIsAddedToCurrentRow) {
|
if (currentDiff > diffIfImageIsAddedToCurrentRow) {
|
||||||
currentRow.aspect += currentAspect;
|
return [
|
||||||
currentRow.images += 1;
|
...acc,
|
||||||
// _rows.push(currentRow);
|
{
|
||||||
continue;
|
aspect: currentRow.aspect + currentAspect,
|
||||||
|
images: currentRow.images + 1,
|
||||||
|
startIndex: currentRow.startIndex,
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
// no-op instead of starting a new row
|
||||||
if (singleRow) {
|
if (singleRow) {
|
||||||
break;
|
return [currentRow];
|
||||||
}
|
}
|
||||||
|
|
||||||
// start a new row
|
// start a new row
|
||||||
_rows.push({
|
return [
|
||||||
|
...acc,
|
||||||
|
currentRow,
|
||||||
|
{
|
||||||
aspect: currentAspect,
|
aspect: currentAspect,
|
||||||
images: 1,
|
images: 1,
|
||||||
startIndex: currentRow.startIndex + currentRow.images,
|
startIndex: currentRow.startIndex + currentRow.images,
|
||||||
});
|
} as Row,
|
||||||
}
|
];
|
||||||
|
},
|
||||||
return R.indexBy(R.prop("startIndex"), _rows);
|
[{ aspect: 0, startIndex: 0, images: 0 }] as Row[]
|
||||||
}, [aspectRatios, targetAspect, singleRow]);
|
),
|
||||||
|
R.indexBy(R.prop("startIndex"))
|
||||||
|
)(aspectRatios),
|
||||||
|
[aspectRatios, targetAspect, singleRow]
|
||||||
|
);
|
||||||
|
|
||||||
const sortedImageList = React.useMemo(
|
const sortedImageList = React.useMemo(
|
||||||
() => _images.map((image) => image.base),
|
() => _images.map((image) => image.base),
|
||||||
@@ -119,11 +134,9 @@ const MasonryGallery = ({
|
|||||||
}
|
}
|
||||||
const rowAspectRatioSum = currentRow.aspect;
|
const rowAspectRatioSum = currentRow.aspect;
|
||||||
const ar = getAspectRatio(image);
|
const ar = getAspectRatio(image);
|
||||||
let width: string;
|
let width;
|
||||||
let height = `calc(${galleryWidth} / ${rowAspectRatioSum} ${
|
let height = `calc(${galleryWidth} / ${rowAspectRatioSum} ${showPalette ? "+ 10px" : "- 10px"})`;
|
||||||
showPalette ? "+ 10px" : "- 10px"
|
if (rowAspectRatioSum < targetAspect * 0.66) {
|
||||||
})`;
|
|
||||||
if (rowAspectRatioSum < targetAspect * 0.66 && !singleRow) {
|
|
||||||
// incomplete row, render stuff at "ideal" sizes instead of filling width
|
// incomplete row, render stuff at "ideal" sizes instead of filling width
|
||||||
width = `calc(calc(100vw - 160px) / ${targetAspect / ar})`;
|
width = `calc(calc(100vw - 160px) / ${targetAspect / ar})`;
|
||||||
height = "unset";
|
height = "unset";
|
||||||
@@ -134,12 +147,10 @@ const MasonryGallery = ({
|
|||||||
const vibrant = getVibrant(image);
|
const vibrant = getVibrant(image);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const img = getImage(image);
|
const img = getImage(image);
|
||||||
|
|
||||||
const data = dataFn ? dataFn(image) : null;
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className="border-8 border-white overflow-hidden relative"
|
className={classNames("border-8 border-white overflow-hidden")}
|
||||||
id={singleRow ? undefined : image.base}
|
id={image.base}
|
||||||
key={`${image.base}`}
|
key={`${image.base}`}
|
||||||
state={{
|
state={{
|
||||||
...linkState,
|
...linkState,
|
||||||
@@ -159,42 +170,22 @@ const MasonryGallery = ({
|
|||||||
}}
|
}}
|
||||||
to={`/photogallery/${image.base}/`}
|
to={`/photogallery/${image.base}/`}
|
||||||
>
|
>
|
||||||
{data && (
|
{debugRating && (
|
||||||
<div className="text-white z-20 absolute flex flex-col items-start">
|
<span className="text-white z-20 absolute bg-black">
|
||||||
{data.map((dataString, i) => (
|
rating: {image.fields?.imageMeta?.meta?.Rating}
|
||||||
<span
|
|
||||||
className="bg-black/30 backdrop-blur shadow p-[2px] m-[2px] max-w-full"
|
|
||||||
key={i}
|
|
||||||
>
|
|
||||||
{dataString}
|
|
||||||
</span>
|
</span>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{img && (
|
{img && (
|
||||||
<div
|
<div className={`h-full ${showPalette && "grid grid-rows-[1fr_20px]"}`}>
|
||||||
className={`h-full ${
|
|
||||||
showPalette && "grid grid-rows-[1fr_20px]"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<GatsbyImage
|
<GatsbyImage
|
||||||
alt={
|
alt={getName(image)}
|
||||||
image.fields?.imageMeta?.meta?.Keywords?.length
|
|
||||||
? `image of ${image.fields?.imageMeta?.meta?.Keywords.join(
|
|
||||||
" and "
|
|
||||||
)}. ${getName(image)}`
|
|
||||||
: getName(image)
|
|
||||||
}
|
|
||||||
className="w-full"
|
className="w-full"
|
||||||
image={img}
|
image={img}
|
||||||
objectFit="cover"
|
objectFit="cover"
|
||||||
/>
|
/>
|
||||||
{showPalette && vibrant && (
|
{ showPalette && vibrant && <div className="grid grid-cols-6 flex-shrink-0 h-[20px] w-full">
|
||||||
<div className="grid grid-cols-6 flex-shrink-0 h-[20px] w-full">
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{ background: `rgba(${vibrant.Vibrant?.join(",")})` }}
|
||||||
background: `rgba(${vibrant.Vibrant?.join(",")})`,
|
|
||||||
}}
|
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -219,8 +210,7 @@ const MasonryGallery = ({
|
|||||||
background: `rgb(${vibrant.DarkMuted?.join(",")})`,
|
background: `rgb(${vibrant.DarkMuted?.join(",")})`,
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
+6
-46
@@ -1,17 +1,16 @@
|
|||||||
import React, { useRef, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import { Link, navigate } from "gatsby";
|
import { Link } from "gatsby";
|
||||||
import { Popover } from "react-tiny-popover";
|
import { Popover } from "react-tiny-popover";
|
||||||
import { StaticImage } from "gatsby-plugin-image";
|
|
||||||
|
|
||||||
const navClasses =
|
const navClasses =
|
||||||
"hover:underline hover:bg-black/10 block p-3 text-black flex-shrink-0 whitespace-nowrap";
|
"hover:underline hover:bg-transparentblack block p-3 text-black";
|
||||||
|
|
||||||
const ExternalLinks = () => (
|
const ExternalLinks = () => (
|
||||||
<ul
|
<ul
|
||||||
className={classnames(
|
className={classnames(
|
||||||
"z-30 overflow-hidden bg-vibrant-dark",
|
"z-30 overflow-hidden bg-vibrant-dark",
|
||||||
"rounded shadow-lg border border-gray-400"
|
"rounded shadow border border-vibrant-light"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
@@ -88,57 +87,18 @@ interface NavProps {
|
|||||||
|
|
||||||
const Nav = ({ internalLinks, className }: NavProps) => {
|
const Nav = ({ internalLinks, className }: NavProps) => {
|
||||||
const [linksMenu, setLinksMenu] = useState(false);
|
const [linksMenu, setLinksMenu] = useState(false);
|
||||||
const faceClicks = useRef(0);
|
|
||||||
const faceLastClicked = useRef(0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
className={classnames(
|
className={classnames(
|
||||||
"my-4 flex flex-col-reverse md:flex-row",
|
"my-4 flex flex-col-reverse md:flex-row items-center w-full font-sans px-4 md:px-8",
|
||||||
"justify-between",
|
|
||||||
"items-center w-full font-sans px-4 md:px-8",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-auto items-center">
|
<div className="md:flex items-baseline flex-auto">
|
||||||
<div
|
|
||||||
className={classnames(
|
|
||||||
"h-[120px] w-[120px] mr-4 my-5 flex-shrink-0"
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
const prevClick = faceLastClicked.current;
|
|
||||||
faceLastClicked.current = Date.now();
|
|
||||||
if (prevClick > 0 && faceLastClicked.current - prevClick > 500) {
|
|
||||||
console.log('too slow!')
|
|
||||||
faceClicks.current = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (faceClicks.current === 4) {
|
|
||||||
navigate("/photogallery/?debug=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
faceClicks.current += 1;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StaticImage
|
|
||||||
alt="A picture of me"
|
|
||||||
className="relative"
|
|
||||||
placeholder="tracedSVG"
|
|
||||||
src="../images/circle-profile.png"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
// top: "-70%",
|
|
||||||
// left: "-50%",
|
|
||||||
// width: "200%",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="items-baseline">
|
|
||||||
<h1 className="font-bold mr-2">Chuck Dries</h1>
|
<h1 className="font-bold mr-2">Chuck Dries</h1>
|
||||||
<h2 className="text-md">Software Engineer & Photographer</h2>
|
<h2 className="text-md">Software Engineer & Photographer</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<ul className="flex">
|
<ul className="flex">
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ export function Select<T extends object>(props: AriaSelectProps<T>) {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
{...mergeProps(buttonProps, focusProps)}
|
{...mergeProps(buttonProps, focusProps)}
|
||||||
className={`py-[5px] px-3 w-[150px] flex flex-row items-center justify-between overflow-hidden cursor-default rounded border hover:bg-black/10 ${
|
className={`py-[5px] px-3 w-[150px] flex flex-row items-center justify-between overflow-hidden cursor-default rounded border hover:bg-transparentblack ${
|
||||||
isFocusVisible ? "border-green-700" : "border-gray-400"
|
isFocusVisible ? "border-green-700" : "border-black"
|
||||||
} ${state.isOpen ? "bg-gray-100" : "bg-white"}`}
|
} ${state.isOpen ? "bg-gray-100" : "bg-white"}`}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import {useToggleState} from 'react-stately';
|
|
||||||
import {AriaToggleButtonProps, useToggleButton} from 'react-aria';
|
|
||||||
import {useRef} from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
export function ToggleButton(props: AriaToggleButtonProps) {
|
|
||||||
let ref = useRef(null);
|
|
||||||
let state = useToggleState(props);
|
|
||||||
let { buttonProps, isPressed } = useToggleButton(props, state, ref);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
{...buttonProps}
|
|
||||||
className={classNames(buttonProps.className, "py-[3px] px-2 mx-1 rounded")}
|
|
||||||
ref={ref}
|
|
||||||
style={{
|
|
||||||
background: isPressed
|
|
||||||
? state.isSelected ? 'darkgreen' : 'gray'
|
|
||||||
: state.isSelected
|
|
||||||
? 'green'
|
|
||||||
: 'lightgray',
|
|
||||||
color: state.isSelected ? 'white' : 'black',
|
|
||||||
userSelect: 'none',
|
|
||||||
WebkitUserSelect: 'none',
|
|
||||||
border: 'none'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Vendored
+2
-366
@@ -571,7 +571,7 @@ type FileFieldsFilterInput = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type FileFieldsImageMeta = {
|
type FileFieldsImageMeta = {
|
||||||
readonly datePublished: Maybe<Scalars['Date']>;
|
readonly datePublished: Maybe<Scalars['String']>;
|
||||||
readonly dateTaken: Maybe<Scalars['Date']>;
|
readonly dateTaken: Maybe<Scalars['Date']>;
|
||||||
readonly dominantHue: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>;
|
readonly dominantHue: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>;
|
||||||
readonly meta: Maybe<FileFieldsImageMetaMeta>;
|
readonly meta: Maybe<FileFieldsImageMetaMeta>;
|
||||||
@@ -580,14 +580,6 @@ type FileFieldsImageMeta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
type FileFieldsImageMeta_datePublishedArgs = {
|
|
||||||
difference: InputMaybe<Scalars['String']>;
|
|
||||||
formatString: InputMaybe<Scalars['String']>;
|
|
||||||
fromNow: InputMaybe<Scalars['Boolean']>;
|
|
||||||
locale: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type FileFieldsImageMeta_dateTakenArgs = {
|
type FileFieldsImageMeta_dateTakenArgs = {
|
||||||
difference: InputMaybe<Scalars['String']>;
|
difference: InputMaybe<Scalars['String']>;
|
||||||
formatString: InputMaybe<Scalars['String']>;
|
formatString: InputMaybe<Scalars['String']>;
|
||||||
@@ -605,7 +597,7 @@ type FileFieldsImageMetaFieldSelector = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type FileFieldsImageMetaFilterInput = {
|
type FileFieldsImageMetaFilterInput = {
|
||||||
readonly datePublished: InputMaybe<DateQueryOperatorInput>;
|
readonly datePublished: InputMaybe<StringQueryOperatorInput>;
|
||||||
readonly dateTaken: InputMaybe<DateQueryOperatorInput>;
|
readonly dateTaken: InputMaybe<DateQueryOperatorInput>;
|
||||||
readonly dominantHue: InputMaybe<FloatQueryOperatorInput>;
|
readonly dominantHue: InputMaybe<FloatQueryOperatorInput>;
|
||||||
readonly meta: InputMaybe<FileFieldsImageMetaMetaFilterInput>;
|
readonly meta: InputMaybe<FileFieldsImageMetaMetaFilterInput>;
|
||||||
@@ -1532,7 +1524,6 @@ type Query = {
|
|||||||
readonly allSiteFunction: SiteFunctionConnection;
|
readonly allSiteFunction: SiteFunctionConnection;
|
||||||
readonly allSitePage: SitePageConnection;
|
readonly allSitePage: SitePageConnection;
|
||||||
readonly allSitePlugin: SitePluginConnection;
|
readonly allSitePlugin: SitePluginConnection;
|
||||||
readonly allStaticImage: StaticImageConnection;
|
|
||||||
readonly directory: Maybe<Directory>;
|
readonly directory: Maybe<Directory>;
|
||||||
readonly file: Maybe<File>;
|
readonly file: Maybe<File>;
|
||||||
readonly imageSharp: Maybe<ImageSharp>;
|
readonly imageSharp: Maybe<ImageSharp>;
|
||||||
@@ -1541,7 +1532,6 @@ type Query = {
|
|||||||
readonly siteFunction: Maybe<SiteFunction>;
|
readonly siteFunction: Maybe<SiteFunction>;
|
||||||
readonly sitePage: Maybe<SitePage>;
|
readonly sitePage: Maybe<SitePage>;
|
||||||
readonly sitePlugin: Maybe<SitePlugin>;
|
readonly sitePlugin: Maybe<SitePlugin>;
|
||||||
readonly staticImage: Maybe<StaticImage>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -1609,14 +1599,6 @@ type Query_allSitePluginArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
type Query_allStaticImageArgs = {
|
|
||||||
filter: InputMaybe<StaticImageFilterInput>;
|
|
||||||
limit: InputMaybe<Scalars['Int']>;
|
|
||||||
skip: InputMaybe<Scalars['Int']>;
|
|
||||||
sort: InputMaybe<ReadonlyArray<InputMaybe<StaticImageSortInput>>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type Query_directoryArgs = {
|
type Query_directoryArgs = {
|
||||||
absolutePath: InputMaybe<StringQueryOperatorInput>;
|
absolutePath: InputMaybe<StringQueryOperatorInput>;
|
||||||
accessTime: InputMaybe<DateQueryOperatorInput>;
|
accessTime: InputMaybe<DateQueryOperatorInput>;
|
||||||
@@ -1786,46 +1768,6 @@ type Query_sitePluginArgs = {
|
|||||||
version: InputMaybe<StringQueryOperatorInput>;
|
version: InputMaybe<StringQueryOperatorInput>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
type Query_staticImageArgs = {
|
|
||||||
absolutePath: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
accessTime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
atime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
atimeMs: InputMaybe<FloatQueryOperatorInput>;
|
|
||||||
base: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
birthTime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
birthtime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
birthtimeMs: InputMaybe<FloatQueryOperatorInput>;
|
|
||||||
blksize: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
blocks: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
changeTime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
children: InputMaybe<NodeFilterListInput>;
|
|
||||||
ctime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
ctimeMs: InputMaybe<FloatQueryOperatorInput>;
|
|
||||||
dev: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
dir: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
ext: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
extension: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
id: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
ino: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
internal: InputMaybe<InternalFilterInput>;
|
|
||||||
mode: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
modifiedTime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
mtime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
mtimeMs: InputMaybe<FloatQueryOperatorInput>;
|
|
||||||
name: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
nlink: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
parent: InputMaybe<NodeFilterInput>;
|
|
||||||
prettySize: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
rdev: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
relativeDirectory: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
relativePath: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
root: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
size: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
sourceInstanceName: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
uid: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Site = Node & {
|
type Site = Node & {
|
||||||
readonly buildTime: Maybe<Scalars['Date']>;
|
readonly buildTime: Maybe<Scalars['Date']>;
|
||||||
readonly children: ReadonlyArray<Node>;
|
readonly children: ReadonlyArray<Node>;
|
||||||
@@ -2592,312 +2534,6 @@ type SortOrderEnum =
|
|||||||
| 'ASC'
|
| 'ASC'
|
||||||
| 'DESC';
|
| 'DESC';
|
||||||
|
|
||||||
type StaticImage = Node & {
|
|
||||||
readonly absolutePath: Maybe<Scalars['String']>;
|
|
||||||
readonly accessTime: Maybe<Scalars['Date']>;
|
|
||||||
readonly atime: Maybe<Scalars['Date']>;
|
|
||||||
readonly atimeMs: Maybe<Scalars['Float']>;
|
|
||||||
readonly base: Maybe<Scalars['String']>;
|
|
||||||
readonly birthTime: Maybe<Scalars['Date']>;
|
|
||||||
readonly birthtime: Maybe<Scalars['Date']>;
|
|
||||||
readonly birthtimeMs: Maybe<Scalars['Float']>;
|
|
||||||
readonly blksize: Maybe<Scalars['Int']>;
|
|
||||||
readonly blocks: Maybe<Scalars['Int']>;
|
|
||||||
readonly changeTime: Maybe<Scalars['Date']>;
|
|
||||||
readonly children: ReadonlyArray<Node>;
|
|
||||||
readonly ctime: Maybe<Scalars['Date']>;
|
|
||||||
readonly ctimeMs: Maybe<Scalars['Float']>;
|
|
||||||
readonly dev: Maybe<Scalars['Int']>;
|
|
||||||
readonly dir: Maybe<Scalars['String']>;
|
|
||||||
readonly ext: Maybe<Scalars['String']>;
|
|
||||||
readonly extension: Maybe<Scalars['String']>;
|
|
||||||
readonly id: Scalars['ID'];
|
|
||||||
readonly ino: Maybe<Scalars['Int']>;
|
|
||||||
readonly internal: Internal;
|
|
||||||
readonly mode: Maybe<Scalars['Int']>;
|
|
||||||
readonly modifiedTime: Maybe<Scalars['Date']>;
|
|
||||||
readonly mtime: Maybe<Scalars['Date']>;
|
|
||||||
readonly mtimeMs: Maybe<Scalars['Float']>;
|
|
||||||
readonly name: Maybe<Scalars['String']>;
|
|
||||||
readonly nlink: Maybe<Scalars['Int']>;
|
|
||||||
readonly parent: Maybe<Node>;
|
|
||||||
readonly prettySize: Maybe<Scalars['String']>;
|
|
||||||
readonly rdev: Maybe<Scalars['Int']>;
|
|
||||||
readonly relativeDirectory: Maybe<Scalars['String']>;
|
|
||||||
readonly relativePath: Maybe<Scalars['String']>;
|
|
||||||
readonly root: Maybe<Scalars['String']>;
|
|
||||||
readonly size: Maybe<Scalars['Int']>;
|
|
||||||
readonly sourceInstanceName: Maybe<Scalars['String']>;
|
|
||||||
readonly uid: Maybe<Scalars['Int']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImage_accessTimeArgs = {
|
|
||||||
difference: InputMaybe<Scalars['String']>;
|
|
||||||
formatString: InputMaybe<Scalars['String']>;
|
|
||||||
fromNow: InputMaybe<Scalars['Boolean']>;
|
|
||||||
locale: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImage_atimeArgs = {
|
|
||||||
difference: InputMaybe<Scalars['String']>;
|
|
||||||
formatString: InputMaybe<Scalars['String']>;
|
|
||||||
fromNow: InputMaybe<Scalars['Boolean']>;
|
|
||||||
locale: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImage_birthTimeArgs = {
|
|
||||||
difference: InputMaybe<Scalars['String']>;
|
|
||||||
formatString: InputMaybe<Scalars['String']>;
|
|
||||||
fromNow: InputMaybe<Scalars['Boolean']>;
|
|
||||||
locale: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImage_birthtimeArgs = {
|
|
||||||
difference: InputMaybe<Scalars['String']>;
|
|
||||||
formatString: InputMaybe<Scalars['String']>;
|
|
||||||
fromNow: InputMaybe<Scalars['Boolean']>;
|
|
||||||
locale: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImage_changeTimeArgs = {
|
|
||||||
difference: InputMaybe<Scalars['String']>;
|
|
||||||
formatString: InputMaybe<Scalars['String']>;
|
|
||||||
fromNow: InputMaybe<Scalars['Boolean']>;
|
|
||||||
locale: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImage_ctimeArgs = {
|
|
||||||
difference: InputMaybe<Scalars['String']>;
|
|
||||||
formatString: InputMaybe<Scalars['String']>;
|
|
||||||
fromNow: InputMaybe<Scalars['Boolean']>;
|
|
||||||
locale: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImage_modifiedTimeArgs = {
|
|
||||||
difference: InputMaybe<Scalars['String']>;
|
|
||||||
formatString: InputMaybe<Scalars['String']>;
|
|
||||||
fromNow: InputMaybe<Scalars['Boolean']>;
|
|
||||||
locale: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImage_mtimeArgs = {
|
|
||||||
difference: InputMaybe<Scalars['String']>;
|
|
||||||
formatString: InputMaybe<Scalars['String']>;
|
|
||||||
fromNow: InputMaybe<Scalars['Boolean']>;
|
|
||||||
locale: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StaticImageConnection = {
|
|
||||||
readonly distinct: ReadonlyArray<Scalars['String']>;
|
|
||||||
readonly edges: ReadonlyArray<StaticImageEdge>;
|
|
||||||
readonly group: ReadonlyArray<StaticImageGroupConnection>;
|
|
||||||
readonly max: Maybe<Scalars['Float']>;
|
|
||||||
readonly min: Maybe<Scalars['Float']>;
|
|
||||||
readonly nodes: ReadonlyArray<StaticImage>;
|
|
||||||
readonly pageInfo: PageInfo;
|
|
||||||
readonly sum: Maybe<Scalars['Float']>;
|
|
||||||
readonly totalCount: Scalars['Int'];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImageConnection_distinctArgs = {
|
|
||||||
field: StaticImageFieldSelector;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImageConnection_groupArgs = {
|
|
||||||
field: StaticImageFieldSelector;
|
|
||||||
limit: InputMaybe<Scalars['Int']>;
|
|
||||||
skip: InputMaybe<Scalars['Int']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImageConnection_maxArgs = {
|
|
||||||
field: StaticImageFieldSelector;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImageConnection_minArgs = {
|
|
||||||
field: StaticImageFieldSelector;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImageConnection_sumArgs = {
|
|
||||||
field: StaticImageFieldSelector;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StaticImageEdge = {
|
|
||||||
readonly next: Maybe<StaticImage>;
|
|
||||||
readonly node: StaticImage;
|
|
||||||
readonly previous: Maybe<StaticImage>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StaticImageFieldSelector = {
|
|
||||||
readonly absolutePath: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly accessTime: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly atime: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly atimeMs: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly base: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly birthTime: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly birthtime: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly birthtimeMs: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly blksize: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly blocks: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly changeTime: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly children: InputMaybe<NodeFieldSelector>;
|
|
||||||
readonly ctime: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly ctimeMs: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly dev: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly dir: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly ext: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly extension: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly id: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly ino: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly internal: InputMaybe<InternalFieldSelector>;
|
|
||||||
readonly mode: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly modifiedTime: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly mtime: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly mtimeMs: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly name: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly nlink: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly parent: InputMaybe<NodeFieldSelector>;
|
|
||||||
readonly prettySize: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly rdev: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly relativeDirectory: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly relativePath: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly root: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly size: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly sourceInstanceName: InputMaybe<FieldSelectorEnum>;
|
|
||||||
readonly uid: InputMaybe<FieldSelectorEnum>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StaticImageFilterInput = {
|
|
||||||
readonly absolutePath: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly accessTime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
readonly atime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
readonly atimeMs: InputMaybe<FloatQueryOperatorInput>;
|
|
||||||
readonly base: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly birthTime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
readonly birthtime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
readonly birthtimeMs: InputMaybe<FloatQueryOperatorInput>;
|
|
||||||
readonly blksize: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
readonly blocks: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
readonly changeTime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
readonly children: InputMaybe<NodeFilterListInput>;
|
|
||||||
readonly ctime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
readonly ctimeMs: InputMaybe<FloatQueryOperatorInput>;
|
|
||||||
readonly dev: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
readonly dir: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly ext: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly extension: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly id: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly ino: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
readonly internal: InputMaybe<InternalFilterInput>;
|
|
||||||
readonly mode: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
readonly modifiedTime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
readonly mtime: InputMaybe<DateQueryOperatorInput>;
|
|
||||||
readonly mtimeMs: InputMaybe<FloatQueryOperatorInput>;
|
|
||||||
readonly name: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly nlink: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
readonly parent: InputMaybe<NodeFilterInput>;
|
|
||||||
readonly prettySize: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly rdev: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
readonly relativeDirectory: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly relativePath: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly root: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly size: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
readonly sourceInstanceName: InputMaybe<StringQueryOperatorInput>;
|
|
||||||
readonly uid: InputMaybe<IntQueryOperatorInput>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StaticImageGroupConnection = {
|
|
||||||
readonly distinct: ReadonlyArray<Scalars['String']>;
|
|
||||||
readonly edges: ReadonlyArray<StaticImageEdge>;
|
|
||||||
readonly field: Scalars['String'];
|
|
||||||
readonly fieldValue: Maybe<Scalars['String']>;
|
|
||||||
readonly group: ReadonlyArray<StaticImageGroupConnection>;
|
|
||||||
readonly max: Maybe<Scalars['Float']>;
|
|
||||||
readonly min: Maybe<Scalars['Float']>;
|
|
||||||
readonly nodes: ReadonlyArray<StaticImage>;
|
|
||||||
readonly pageInfo: PageInfo;
|
|
||||||
readonly sum: Maybe<Scalars['Float']>;
|
|
||||||
readonly totalCount: Scalars['Int'];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImageGroupConnection_distinctArgs = {
|
|
||||||
field: StaticImageFieldSelector;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImageGroupConnection_groupArgs = {
|
|
||||||
field: StaticImageFieldSelector;
|
|
||||||
limit: InputMaybe<Scalars['Int']>;
|
|
||||||
skip: InputMaybe<Scalars['Int']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImageGroupConnection_maxArgs = {
|
|
||||||
field: StaticImageFieldSelector;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImageGroupConnection_minArgs = {
|
|
||||||
field: StaticImageFieldSelector;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type StaticImageGroupConnection_sumArgs = {
|
|
||||||
field: StaticImageFieldSelector;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StaticImageSortInput = {
|
|
||||||
readonly absolutePath: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly accessTime: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly atime: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly atimeMs: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly base: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly birthTime: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly birthtime: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly birthtimeMs: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly blksize: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly blocks: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly changeTime: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly children: InputMaybe<NodeSortInput>;
|
|
||||||
readonly ctime: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly ctimeMs: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly dev: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly dir: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly ext: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly extension: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly id: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly ino: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly internal: InputMaybe<InternalSortInput>;
|
|
||||||
readonly mode: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly modifiedTime: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly mtime: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly mtimeMs: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly name: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly nlink: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly parent: InputMaybe<NodeSortInput>;
|
|
||||||
readonly prettySize: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly rdev: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly relativeDirectory: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly relativePath: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly root: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly size: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly sourceInstanceName: InputMaybe<SortOrderEnum>;
|
|
||||||
readonly uid: InputMaybe<SortOrderEnum>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StringQueryOperatorInput = {
|
type StringQueryOperatorInput = {
|
||||||
readonly eq: InputMaybe<Scalars['String']>;
|
readonly eq: InputMaybe<Scalars['String']>;
|
||||||
readonly glob: InputMaybe<Scalars['String']>;
|
readonly glob: InputMaybe<Scalars['String']>;
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 22 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 466 KiB |
Binary file not shown.
+2
-2
@@ -102,8 +102,8 @@ const IndexPage = ({
|
|||||||
objectFit={browserIsLandscape ? "cover" : "contain"}
|
objectFit={browserIsLandscape ? "cover" : "contain"}
|
||||||
style={{
|
style={{
|
||||||
height: screenHeight
|
height: screenHeight
|
||||||
? `${screenHeight - 268}px`
|
? `${screenHeight - 160}px`
|
||||||
: "calc(100vh-268px)",
|
: "calc(100vh-160px)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
+88
-153
@@ -1,7 +1,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { graphql, Link, navigate, PageProps } from "gatsby";
|
import { graphql, PageProps } from "gatsby";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
|
// import { Picker, Item } from "@adobe/react-spectrum";
|
||||||
|
|
||||||
import MasonryGallery from "../components/MasonryGallery";
|
import MasonryGallery from "../components/MasonryGallery";
|
||||||
import KeywordsPicker from "../components/KeywordsPicker";
|
import KeywordsPicker from "../components/KeywordsPicker";
|
||||||
@@ -15,7 +16,6 @@ import Nav from "../components/Nav";
|
|||||||
import { Item, Select } from "../components/Select";
|
import { Item, Select } from "../components/Select";
|
||||||
import { Switch } from "../components/Switch";
|
import { Switch } from "../components/Switch";
|
||||||
import ColorPalette from "@spectrum-icons/workflow/ColorPalette";
|
import ColorPalette from "@spectrum-icons/workflow/ColorPalette";
|
||||||
import { ToggleButton } from "../components/ToggleButton";
|
|
||||||
|
|
||||||
const SORT_KEYS = {
|
const SORT_KEYS = {
|
||||||
hue: ["fields", "imageMeta", "vibrantHue"],
|
hue: ["fields", "imageMeta", "vibrantHue"],
|
||||||
@@ -23,38 +23,37 @@ const SORT_KEYS = {
|
|||||||
// hue_debug: ["fields", "imageMeta", "dominantHue", 0],
|
// hue_debug: ["fields", "imageMeta", "dominantHue", 0],
|
||||||
hue_debug: ["fields", "imageMeta", "dominantHue", "0"],
|
hue_debug: ["fields", "imageMeta", "dominantHue", "0"],
|
||||||
date: ["fields", "imageMeta", "dateTaken"],
|
date: ["fields", "imageMeta", "dateTaken"],
|
||||||
datePublished: ["fields", "imageMeta", "datePublished"],
|
modified: ["fields", "imageMeta", "datePublished"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type GalleryImage =
|
export type GalleryImage =
|
||||||
Queries.GalleryPageQueryQuery["all"]["nodes"][number];
|
Queries.GalleryPageQueryQuery["all"]["nodes"][number];
|
||||||
|
|
||||||
function smartCompareDates(
|
function smartCompareDates(key: keyof typeof SORT_KEYS, left: GalleryImage, right: GalleryImage) {
|
||||||
key: keyof typeof SORT_KEYS,
|
|
||||||
left: GalleryImage,
|
|
||||||
right: GalleryImage
|
|
||||||
) {
|
|
||||||
let diff = compareDates(SORT_KEYS[key], left, right);
|
let diff = compareDates(SORT_KEYS[key], left, right);
|
||||||
|
console.log("🚀 ~ file: photogallery.tsx:34 ~ smartCompareDates ~ diff:", diff)
|
||||||
if (diff !== 0) {
|
if (diff !== 0) {
|
||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
|
console.log('falling back to date')
|
||||||
return compareDates(SORT_KEYS.date, left, right);
|
return compareDates(SORT_KEYS.date, left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
const GalleryPage = ({
|
const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||||
data,
|
const hash =
|
||||||
location,
|
typeof window !== "undefined" ? window.location.hash.replace("#", "") : "";
|
||||||
}: PageProps<Queries.GalleryPageQueryQuery>) => {
|
|
||||||
const hash = location.hash ? location.hash.replace("#", "") : "";
|
|
||||||
|
|
||||||
const params = new URLSearchParams(location.search);
|
const [hashCleared, setHashCleared] = React.useState(false); // eslint-disable-line no-unused-vars
|
||||||
const filterKeyword = params.get("filter");
|
// ^ used just to force a re-render with the cleared hash value (I know, it's a smell for sure)
|
||||||
const sortKey = params.get("sort") ?? "rating";
|
const [filterKeyword, _setKeyword] = React.useState(null as string | null);
|
||||||
|
const [sortKey, _setSortKey] = React.useState("rating" as string);
|
||||||
const showDebug = Boolean(params.get("debug")?.length);
|
const showDebug =
|
||||||
|
typeof window !== "undefined" &&
|
||||||
|
window.location.search.includes("debug=true");
|
||||||
const [showPalette, setShowPalette] = React.useState(false);
|
const [showPalette, setShowPalette] = React.useState(false);
|
||||||
|
|
||||||
const onKeywordPick = React.useCallback((newKeyword: string | null) => {
|
const setKeyword = React.useCallback(
|
||||||
|
(newKeyword: string | null) => {
|
||||||
if (newKeyword) {
|
if (newKeyword) {
|
||||||
try {
|
try {
|
||||||
window.plausible("Filter Keyword", {
|
window.plausible("Filter Keyword", {
|
||||||
@@ -64,7 +63,15 @@ const GalleryPage = ({
|
|||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
_setKeyword(newKeyword);
|
||||||
|
window.history.replaceState(
|
||||||
|
null,
|
||||||
|
"",
|
||||||
|
getGalleryPageUrl({ keyword: newKeyword, sortKey }, hash)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[_setKeyword, sortKey, hash]
|
||||||
|
);
|
||||||
|
|
||||||
const setSortKey = React.useCallback(
|
const setSortKey = React.useCallback(
|
||||||
(newSortKey: string) => {
|
(newSortKey: string) => {
|
||||||
@@ -75,65 +82,67 @@ const GalleryPage = ({
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
navigate(
|
_setSortKey(newSortKey);
|
||||||
getGalleryPageUrl(
|
window.history.replaceState(
|
||||||
{ sortKey: newSortKey, keyword: filterKeyword, showDebug },
|
null,
|
||||||
hash
|
"",
|
||||||
)
|
getGalleryPageUrl({ sortKey: newSortKey, keyword: filterKeyword }, hash)
|
||||||
// { replace: true }
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[filterKeyword, hash, showDebug]
|
[_setSortKey, filterKeyword, hash]
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeHash = React.useCallback(() => {
|
const removeHash = React.useCallback(() => {
|
||||||
if (!hash.length) {
|
const url = new URL(
|
||||||
return;
|
typeof window !== "undefined"
|
||||||
}
|
? window.location.href.toString()
|
||||||
console.log('remove hash')
|
: "https://chuckdries.com/photogallery/"
|
||||||
navigate(
|
|
||||||
getGalleryPageUrl({ sortKey, keyword: filterKeyword, showDebug }, ""),
|
|
||||||
{ replace: true }
|
|
||||||
);
|
);
|
||||||
window.removeEventListener("scroll", removeHash);
|
|
||||||
}, [hash, sortKey, filterKeyword, showDebug]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
url.hash = "";
|
||||||
// window.addEventListener("scroll", removeHash);
|
window.history.replaceState(null, "", url.href.toString());
|
||||||
return () => {
|
window.removeEventListener("wheel", removeHash);
|
||||||
window.removeEventListener("scroll", removeHash);
|
setHashCleared(true);
|
||||||
};
|
}, []);
|
||||||
}, [removeHash]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
const scrollIntoView = React.useCallback(() => {
|
||||||
// hacky but it works for now
|
if (!hash) {
|
||||||
requestAnimationFrame(() => {
|
|
||||||
// don't scroll into view if user got here with back button or if we just cleared it
|
|
||||||
if (!hash.length) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const el = document.getElementById(hash);
|
const el = document.getElementById(hash);
|
||||||
if (!el) {
|
if (!el) {
|
||||||
console.log("⚠️failed to find hash");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("scrolling into view manually", el.offsetTop);
|
|
||||||
el.scrollIntoView({
|
el.scrollIntoView({
|
||||||
block: hash.startsWith("all") ? "start" : "center",
|
block: "center",
|
||||||
behavior: hash.startsWith("all") ? "smooth" : "auto",
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
window.addEventListener("scroll", removeHash);
|
|
||||||
}, 100);
|
|
||||||
});
|
});
|
||||||
|
window.addEventListener("wheel", removeHash);
|
||||||
}, [hash, 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 images: GalleryImage[] = React.useMemo(() => {
|
||||||
const sort =
|
const sort =
|
||||||
sortKey === "date" || sortKey === "datePublished"
|
sortKey === "date" || sortKey === "modified"
|
||||||
? R.sort((node1: typeof data["all"]["nodes"][number], node2) =>
|
? R.sort((node1: typeof data["all"]["nodes"][number], node2) => smartCompareDates(sortKey, node1, node2))
|
||||||
smartCompareDates(sortKey, node1, node2)
|
|
||||||
)
|
|
||||||
: R.sort(
|
: R.sort(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
R.descend(R.path<GalleryImage>(SORT_KEYS[sortKey]))
|
R.descend(R.path<GalleryImage>(SORT_KEYS[sortKey]))
|
||||||
@@ -162,50 +171,8 @@ const GalleryPage = ({
|
|||||||
}, [data, sortKey, filterKeyword]);
|
}, [data, sortKey, filterKeyword]);
|
||||||
|
|
||||||
const recents = React.useMemo(() => {
|
const recents = React.useMemo(() => {
|
||||||
return R.sort(
|
return R.sort((left, right) => smartCompareDates('modified', left, right), data.recents.nodes)
|
||||||
(left, right) => smartCompareDates("datePublished", left, right),
|
}, [data, 'hi'])
|
||||||
data.recents.nodes
|
|
||||||
);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const [dbgTags, setDbgTags] = React.useState(false);
|
|
||||||
const [dbgSortKey, setDbgSortKey] = React.useState(false);
|
|
||||||
const [dbgName, setDbgName] = React.useState(false);
|
|
||||||
const dataFn = React.useCallback(
|
|
||||||
(image: GalleryImage): string[] | null => {
|
|
||||||
if (!showDebug) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let data: string[] = [];
|
|
||||||
if (dbgName) {
|
|
||||||
data.push(image.base);
|
|
||||||
}
|
|
||||||
if (dbgSortKey) {
|
|
||||||
switch (sortKey) {
|
|
||||||
case "hue":
|
|
||||||
case "rating": {
|
|
||||||
data.push(R.pathOr("x", SORT_KEYS[sortKey], image));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "date":
|
|
||||||
case "datePublished": {
|
|
||||||
const date = R.pathOr(null, SORT_KEYS[sortKey], image);
|
|
||||||
if (date) {
|
|
||||||
data.push(new Date(date).toLocaleString());
|
|
||||||
} else {
|
|
||||||
data.push("x");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dbgTags) {
|
|
||||||
data.push(image.fields?.imageMeta?.meta?.Keywords?.join(",") ?? "x");
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
[showDebug, sortKey, dbgName, dbgSortKey, dbgTags]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -238,50 +205,31 @@ const GalleryPage = ({
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="gradient pb-6">
|
<div className="px-4 md:px-8">
|
||||||
<div className="px-4 md:px-8 flex items-baseline">
|
<h3 id="recently" className="mx-2 font-bold">
|
||||||
<h3 className="mx-2 font-bold" id="recently">
|
|
||||||
Recently published
|
Recently published
|
||||||
</h3>
|
</h3>
|
||||||
{sortKey !== "datePublished" && (
|
|
||||||
<Link
|
|
||||||
className="underline cursor-pointer text-gray-500"
|
|
||||||
to="?sort=datePublished#all"
|
|
||||||
>
|
|
||||||
show more
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<MasonryGallery
|
<MasonryGallery
|
||||||
aspectsByBreakpoint={{
|
aspectsByBreakpoint={{
|
||||||
xs: 3,
|
xs: 2,
|
||||||
sm: 3,
|
sm: 2,
|
||||||
md: 4,
|
md: 3,
|
||||||
lg: 4,
|
lg: 4,
|
||||||
xl: 5,
|
xl: 5,
|
||||||
"2xl": 6,
|
"2xl": 6.1,
|
||||||
"3xl": 8,
|
"3xl": 8,
|
||||||
}}
|
}}
|
||||||
images={recents}
|
images={recents}
|
||||||
linkState={{
|
|
||||||
sortKey: 'datePublished',
|
|
||||||
filterKeyword,
|
|
||||||
}}
|
|
||||||
singleRow
|
singleRow
|
||||||
/>
|
/>
|
||||||
</div>
|
<div className="px-4 md:px-8 mt-4 pt-2 border-t">
|
||||||
<div className="px-4 md:px-8 mt-2 pt-2">
|
<h3 id="all" className="mx-2 font-bold">
|
||||||
<h3 className="mx-2 font-bold" id="all">
|
|
||||||
All images
|
All images
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col lg:flex-row lg:items-end justify-between px-4 md:px-8 sm:mx-auto">
|
<div className="flex flex-col lg:flex-row lg:items-end justify-between px-4 md:px-8 sm:mx-auto">
|
||||||
<KeywordsPicker
|
<KeywordsPicker
|
||||||
getHref={(val, selected) =>
|
|
||||||
selected
|
|
||||||
? getGalleryPageUrl({ keyword: null, sortKey, showDebug }, hash)
|
|
||||||
: getGalleryPageUrl({ keyword: val, sortKey, showDebug }, hash)
|
|
||||||
}
|
|
||||||
keywords={[
|
keywords={[
|
||||||
"Boyce Thompson Arboretum",
|
"Boyce Thompson Arboretum",
|
||||||
"winter",
|
"winter",
|
||||||
@@ -291,31 +239,18 @@ const GalleryPage = ({
|
|||||||
"landscape",
|
"landscape",
|
||||||
"flowers",
|
"flowers",
|
||||||
"product",
|
"product",
|
||||||
// "waterfall",
|
"waterfall",
|
||||||
// "fireworks",
|
"fireworks",
|
||||||
// "panoramic",
|
"panoramic",
|
||||||
"Portland Japanese Garden",
|
"Portland Japanese Garden",
|
||||||
// "shoot the light",
|
// "shoot the light",
|
||||||
// "sunset",
|
// "sunset",
|
||||||
]}
|
]}
|
||||||
onPick={onKeywordPick}
|
onChange={setKeyword}
|
||||||
value={filterKeyword}
|
value={filterKeyword}
|
||||||
/>
|
/>
|
||||||
<div className="my-2 mx-2 flex flex-row items-end">
|
<div className="m-2 flex flex-row items-end">
|
||||||
{showDebug && (
|
<div className="border border-black rounded mr-2">
|
||||||
<div className="mr-2">
|
|
||||||
<ToggleButton isSelected={dbgName} onChange={setDbgName}>
|
|
||||||
name
|
|
||||||
</ToggleButton>
|
|
||||||
<ToggleButton isSelected={dbgSortKey} onChange={setDbgSortKey}>
|
|
||||||
sort key
|
|
||||||
</ToggleButton>
|
|
||||||
<ToggleButton isSelected={dbgTags} onChange={setDbgTags}>
|
|
||||||
tags
|
|
||||||
</ToggleButton>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="border border-gray-400 rounded mr-2">
|
|
||||||
<Switch
|
<Switch
|
||||||
isSelected={showPalette}
|
isSelected={showPalette}
|
||||||
onChange={(val) => setShowPalette(val)}
|
onChange={(val) => setShowPalette(val)}
|
||||||
@@ -330,12 +265,12 @@ const GalleryPage = ({
|
|||||||
</div>
|
</div>
|
||||||
<Select
|
<Select
|
||||||
label="Sort by..."
|
label="Sort by..."
|
||||||
// @ts-expect-error React.key, but string is more convenient for the state
|
// @ts-ignore
|
||||||
onSelectionChange={setSortKey}
|
onSelectionChange={setSortKey}
|
||||||
selectedKey={sortKey}
|
selectedKey={sortKey}
|
||||||
>
|
>
|
||||||
<Item key="rating">Curated</Item>
|
<Item key="rating">Curated</Item>
|
||||||
<Item key="datePublished">Date published</Item>
|
<Item key="modified">Date published</Item>
|
||||||
<Item key="date">Date taken</Item>
|
<Item key="date">Date taken</Item>
|
||||||
<Item key="hue">Hue</Item>
|
<Item key="hue">Hue</Item>
|
||||||
</Select>
|
</Select>
|
||||||
@@ -352,8 +287,8 @@ const GalleryPage = ({
|
|||||||
"2xl": 6.1,
|
"2xl": 6.1,
|
||||||
"3xl": 8,
|
"3xl": 8,
|
||||||
}}
|
}}
|
||||||
dataFn={dataFn}
|
|
||||||
debugHue={sortKey === "hue_debug"}
|
debugHue={sortKey === "hue_debug"}
|
||||||
|
debugRating={sortKey === "rating" && showDebug}
|
||||||
images={images}
|
images={images}
|
||||||
linkState={{
|
linkState={{
|
||||||
sortKey,
|
sortKey,
|
||||||
@@ -370,7 +305,7 @@ export const query = graphql`
|
|||||||
recents: allFile(
|
recents: allFile(
|
||||||
filter: { sourceInstanceName: { eq: "gallery" } }
|
filter: { sourceInstanceName: { eq: "gallery" } }
|
||||||
sort: { fields: { imageMeta: { datePublished: DESC } } }
|
sort: { fields: { imageMeta: { datePublished: DESC } } }
|
||||||
limit: 10
|
limit: 7
|
||||||
) {
|
) {
|
||||||
...GalleryImageFile
|
...GalleryImageFile
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-6
@@ -9,9 +9,6 @@
|
|||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
:root {
|
|
||||||
/* scroll-behavior: smooth; */
|
|
||||||
}
|
|
||||||
/* .hero * {
|
/* .hero * {
|
||||||
transition: color .2s, background-color .2s;
|
transition: color .2s, background-color .2s;
|
||||||
} */
|
} */
|
||||||
@@ -86,15 +83,27 @@
|
|||||||
.gradient {
|
.gradient {
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
hsla(0deg, 0%, 0%, 0%) 95%,
|
hsl(31deg 24% 44%) 0%,
|
||||||
hsla(0deg, 0%, 0%, 10%) 100%
|
hsl(27deg 25% 42%) 7%,
|
||||||
|
hsl(22deg 26% 40%) 14%,
|
||||||
|
hsl(18deg 27% 37%) 21%,
|
||||||
|
hsl(12deg 28% 35%) 29%,
|
||||||
|
hsl(9deg 29% 32%) 36%,
|
||||||
|
hsl(4deg 30% 30%) 43%,
|
||||||
|
hsl(0deg 31% 27%) 50%,
|
||||||
|
hsl(0deg 31% 23%) 57%,
|
||||||
|
hsl(0deg 31% 19%) 64%,
|
||||||
|
hsl(0deg 32% 15%) 71%,
|
||||||
|
hsl(0deg 30% 12%) 79%,
|
||||||
|
hsl(0deg 30% 8%) 86%,
|
||||||
|
hsl(0deg 30% 4%) 93%,
|
||||||
|
hsl(0deg 0% 0%) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-gray-100;
|
@apply bg-gray-100;
|
||||||
overflow: auto;
|
|
||||||
/* @apply bg-black; */
|
/* @apply bg-black; */
|
||||||
/* @apply text-white; */
|
/* @apply text-white; */
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-36
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useRef } from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { pathOr } from "ramda";
|
import { pathOr } from "ramda";
|
||||||
// import kebabCase from 'lodash/kebabCase';
|
// import kebabCase from 'lodash/kebabCase';
|
||||||
@@ -101,12 +101,11 @@ export const getShutterFractionFromExposureTime = (exposureTime: number) => {
|
|||||||
|
|
||||||
interface galleryPageUrlProps {
|
interface galleryPageUrlProps {
|
||||||
keyword: string | null;
|
keyword: string | null;
|
||||||
sortKey: string | null;
|
sortKey: string;
|
||||||
showDebug: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getGalleryPageUrl = (
|
export const getGalleryPageUrl = (
|
||||||
{ keyword, sortKey, showDebug }: galleryPageUrlProps,
|
{ keyword, sortKey }: galleryPageUrlProps,
|
||||||
hash: string
|
hash: string
|
||||||
) => {
|
) => {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
@@ -124,15 +123,12 @@ export const getGalleryPageUrl = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sortKey !== undefined) {
|
if (sortKey !== undefined) {
|
||||||
if (sortKey === "rating" || sortKey === null) {
|
if (sortKey === "rating") {
|
||||||
url.searchParams.delete("sort");
|
url.searchParams.delete("sort");
|
||||||
} else {
|
} else {
|
||||||
url.searchParams.set("sort", sortKey);
|
url.searchParams.set("sort", sortKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (showDebug) {
|
|
||||||
url.searchParams.set("debug", "true");
|
|
||||||
}
|
|
||||||
if (hash) {
|
if (hash) {
|
||||||
url.hash = hash;
|
url.hash = hash;
|
||||||
}
|
}
|
||||||
@@ -145,34 +141,8 @@ export function compareDates<T>(
|
|||||||
right: T
|
right: T
|
||||||
): number {
|
): number {
|
||||||
// why tf do my dates have newlines in them?!?!
|
// why tf do my dates have newlines in them?!?!
|
||||||
const date1 = new Date(pathOr("", date_path, left).replace(/\s/g, ""));
|
const date1 = new Date(pathOr("", date_path, left).replace(/\s/g, ''));
|
||||||
const date2 = new Date(pathOr("", date_path, right).replace(/\s/g, ""));
|
const date2 = new Date(pathOr("", date_path, right).replace(/\s/g, ''));
|
||||||
const diff = -1 * (date1.getTime() - date2.getTime());
|
const diff = -1 * (date1.getTime() - date2.getTime());
|
||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a memoized function that will only call the passed function when it hasn't been called for the wait period
|
|
||||||
* @param func The function to be called
|
|
||||||
* @param wait Wait period after function hasn't been called for
|
|
||||||
* @returns A memoized function that is debounced
|
|
||||||
*/
|
|
||||||
export const useDebouncedCallback = (func: Function, wait: number) => {
|
|
||||||
// Use a ref to store the timeout between renders
|
|
||||||
// and prevent changes to it from causing re-renders
|
|
||||||
const timeout = useRef<ReturnType<typeof setTimeout>>();
|
|
||||||
|
|
||||||
return useCallback(
|
|
||||||
(...args: any) => {
|
|
||||||
const later = () => {
|
|
||||||
clearTimeout(timeout.current!);
|
|
||||||
func(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
clearTimeout(timeout.current ?? undefined);
|
|
||||||
timeout.current = setTimeout(later, wait);
|
|
||||||
},
|
|
||||||
[func, wait]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -4560,22 +4560,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/lodash.debounce@npm:^4.0.7":
|
|
||||||
version: 4.0.7
|
|
||||||
resolution: "@types/lodash.debounce@npm:4.0.7"
|
|
||||||
dependencies:
|
|
||||||
"@types/lodash": "*"
|
|
||||||
checksum: e873b2d77f89010876baba3437ef826b17221b98948e00b5590828334a481dea1c8f9d28543210e564adc53199584f42c3cb171f8b6c3614fefc0b4e0888679c
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@types/lodash@npm:*":
|
|
||||||
version: 4.14.191
|
|
||||||
resolution: "@types/lodash@npm:4.14.191"
|
|
||||||
checksum: ba0d5434e10690869f32d5ea49095250157cae502f10d57de0a723fd72229ce6c6a4979576f0f13e0aa9fbe3ce2457bfb9fa7d4ec3d6daba56730a51906d1491
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@types/lodash@npm:^4.14.53":
|
"@types/lodash@npm:^4.14.53":
|
||||||
version: 4.14.170
|
version: 4.14.170
|
||||||
resolution: "@types/lodash@npm:4.14.170"
|
resolution: "@types/lodash@npm:4.14.170"
|
||||||
@@ -6480,7 +6464,6 @@ __metadata:
|
|||||||
"@react-spectrum/provider": ^3.6.0
|
"@react-spectrum/provider": ^3.6.0
|
||||||
"@spectrum-icons/workflow": ^4.0.4
|
"@spectrum-icons/workflow": ^4.0.4
|
||||||
"@types/chroma-js": ^2.1.4
|
"@types/chroma-js": ^2.1.4
|
||||||
"@types/lodash.debounce": ^4.0.7
|
|
||||||
"@types/node": ^18.8.3
|
"@types/node": ^18.8.3
|
||||||
"@types/ramda": ^0.28.15
|
"@types/ramda": ^0.28.15
|
||||||
"@types/react": ^18.0.21
|
"@types/react": ^18.0.21
|
||||||
@@ -6512,7 +6495,6 @@ __metadata:
|
|||||||
gatsby-source-filesystem: ^5.0.0
|
gatsby-source-filesystem: ^5.0.0
|
||||||
gatsby-transformer-sharp: ^5.0.0
|
gatsby-transformer-sharp: ^5.0.0
|
||||||
kebab-case: ^1.0.1
|
kebab-case: ^1.0.1
|
||||||
lodash.debounce: ^4.0.8
|
|
||||||
node-iptc: ^1.0.5
|
node-iptc: ^1.0.5
|
||||||
node-vibrant: 3.1.6
|
node-vibrant: 3.1.6
|
||||||
postcss: ^8.4.19
|
postcss: ^8.4.19
|
||||||
|
|||||||
Reference in New Issue
Block a user