Compare commits
6 Commits
main
...
recent-ima
Author | SHA1 | Date | |
---|---|---|---|
534ee35451 | |||
fa50031f0a | |||
1f9fa7b204 | |||
496185cc88 | |||
e046649be1 | |||
6214944649 |
Binary file not shown.
Binary file not shown.
BIN
data/gallery/DSC00003.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC00003.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC00015.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC00015.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC00159-2.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC00159-2.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC00512.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC00512.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC01832.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC01832.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC02538-2.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC02538-2.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC03049.jpg
(Stored with Git LFS)
Normal file
BIN
data/gallery/DSC03049.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/gallery/DSC03401.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC03401.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC04122.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC04122.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC05702.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC05702.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC06124.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC06124.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC06515.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC06515.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC06719.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC06719.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC06803.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC06803.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC08086.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC08086.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC08103.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC08103.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC08222.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC08222.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC08263.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC08263.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC08588.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC08588.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC09447.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC09447.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC09453.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC09453.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/DSC09454.jpg
(Stored with Git LFS)
BIN
data/gallery/DSC09454.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/_DSC6060.jpg
(Stored with Git LFS)
BIN
data/gallery/_DSC6060.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/_DSC6062.jpg
(Stored with Git LFS)
BIN
data/gallery/_DSC6062.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
data/gallery/_DSC6066.jpg
(Stored with Git LFS)
BIN
data/gallery/_DSC6066.jpg
(Stored with Git LFS)
Binary file not shown.
@ -14,25 +14,6 @@ export const onRouteUpdate = function () {
|
||||
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 { MDXProvider } from '@mdx-js/react';
|
||||
|
||||
|
@ -10,9 +10,9 @@ import sharp from "sharp";
|
||||
import { Palette } from "node-vibrant/lib/color";
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
import util from "node:util";
|
||||
import util from 'node:util';
|
||||
import { exec as _exec } from "child_process";
|
||||
const exec = util.promisify(_exec);
|
||||
const exec = util.promisify(_exec)
|
||||
|
||||
// const path = require("path");
|
||||
// const Vibrant = require("node-vibrant");
|
||||
@ -119,7 +119,7 @@ function transformMetaToNodeData(
|
||||
vibrantData: Palette,
|
||||
imagePath: string,
|
||||
{ r, g, b }: { r: number; b: number; g: number },
|
||||
datePublished: string
|
||||
datePublished: string,
|
||||
) {
|
||||
const vibrant = vibrantData ? processColors(vibrantData, imagePath) : null;
|
||||
const vibrantHue = vibrantData.Vibrant!.getHsl()[0] * 360;
|
||||
@ -180,34 +180,18 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async function ({
|
||||
const { createNodeField } = actions;
|
||||
|
||||
if (node.internal.type === "File" && node.sourceInstanceName === "gallery") {
|
||||
const { stdout: datePublished, stderr } = await exec(
|
||||
`git log --diff-filter=A --follow --format=%aI -1 -- ${node.absolutePath}`
|
||||
);
|
||||
|
||||
const { stdout: datePublished, stderr } = await exec(`git log --diff-filter=A --follow --format=%aI -1 -- ${node.absolutePath}`)
|
||||
if (stderr.length) {
|
||||
console.error("something went wrong checking publish date: ", stderr);
|
||||
console.error('something went wrong checking publish date: ', stderr);
|
||||
}
|
||||
|
||||
let metaData;
|
||||
try {
|
||||
metaData = await exifr.parse(node.absolutePath as string, {
|
||||
iptc: true,
|
||||
xmp: true,
|
||||
// icc: true
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`something wen wrong with exifr on image ${node.base}`, e);
|
||||
throw e;
|
||||
}
|
||||
const metaData = await exifr.parse(node.absolutePath as string, {
|
||||
iptc: true,
|
||||
xmp: true,
|
||||
// icc: true
|
||||
});
|
||||
|
||||
let sharpImage: sharp.Sharp;
|
||||
|
||||
try {
|
||||
sharpImage = sharp(node.absolutePath as string);
|
||||
} catch (e) {
|
||||
console.error(`something wen wrong with sharp on image ${node.base}`, e);
|
||||
throw e;
|
||||
}
|
||||
const sharpImage = sharp(node.absolutePath as string);
|
||||
const { dominant } = await sharpImage.stats();
|
||||
const resizedImage = await sharpImage
|
||||
.resize({
|
||||
@ -229,8 +213,7 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async function ({
|
||||
vibrantData,
|
||||
node.absolutePath as string,
|
||||
dominant,
|
||||
// if datePublished is empty, image has not been committed to git yet and is thus brand new
|
||||
datePublished.length ? datePublished.replace("\n", "") : new Date().toISOString()
|
||||
datePublished
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@ -41,7 +41,6 @@
|
||||
"gatsby-source-filesystem": "^5.0.0",
|
||||
"gatsby-transformer-sharp": "^5.0.0",
|
||||
"kebab-case": "^1.0.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"node-iptc": "^1.0.5",
|
||||
"node-vibrant": "3.1.6",
|
||||
"postcss": "^8.4.19",
|
||||
@ -61,7 +60,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chroma-js": "^2.1.4",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/node": "^18.8.3",
|
||||
"@types/ramda": "^0.28.15",
|
||||
"@types/react": "^18.0.21",
|
||||
|
@ -63,15 +63,6 @@ const GalleryImage = ({ data, location: { state } }) => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
window.scrollTo({
|
||||
top: 180,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}, 50);
|
||||
}, [image.base]);
|
||||
|
||||
const nextIndex =
|
||||
sortedImageList && currentIndex < sortedImageList.length
|
||||
? currentIndex + 1
|
||||
@ -144,7 +135,7 @@ const GalleryImage = ({ data, location: { state } }) => {
|
||||
locationString = location.join(", ");
|
||||
}
|
||||
const vibrant = getVibrant(image, true);
|
||||
const BLEND = "hsl";
|
||||
const BLEND = 'hsl'
|
||||
const darkAccent = chroma
|
||||
.mix(vibrant.Vibrant, "hsla(216, 0%, 90%, 1)", 0.6, BLEND)
|
||||
.hex();
|
||||
@ -154,7 +145,7 @@ const GalleryImage = ({ data, location: { state } }) => {
|
||||
ar > 1
|
||||
? "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(
|
||||
() =>
|
||||
|
@ -1,37 +1,44 @@
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
import { Link } from "gatsby";
|
||||
import Checkmark from "@spectrum-icons/workflow/Checkmark";
|
||||
|
||||
interface KeywordsPickerProps {
|
||||
keywords: string[];
|
||||
value: string | null;
|
||||
getHref: (value: string | null, selected: boolean) => string;
|
||||
onPick: (value: string | null) => void;
|
||||
onChange: (val: string | null) => void;
|
||||
}
|
||||
const KeywordsPicker = ({ keywords, value, getHref, onPick }: KeywordsPickerProps) => {
|
||||
const KeywordsPicker = ({ keywords, value, onChange }: KeywordsPickerProps) => {
|
||||
return (
|
||||
<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">
|
||||
{keywords.map((keyword) => {
|
||||
const selected = value === keyword;
|
||||
return (
|
||||
<li key={keyword}>
|
||||
<Link
|
||||
<button
|
||||
className={classNames(
|
||||
`py-[5px] px-3 rounded-full text-sm block`,
|
||||
`text-black border border-gray-400`,
|
||||
`py-[5px] px-3 rounded-full text-sm`,
|
||||
`text-black border border-black`,
|
||||
selected
|
||||
? "bg-black/10 font-bold"
|
||||
? "bg-transparentblack font-bold"
|
||||
: `bg-white
|
||||
hover:bg-black/10`
|
||||
hover:bg-transparentblack`
|
||||
)}
|
||||
onClick={() => onPick(keyword)}
|
||||
replace={false}
|
||||
to={getHref(keyword, selected)}
|
||||
onClick={() => (selected ? onChange(null) : onChange(keyword))}
|
||||
type="button"
|
||||
>
|
||||
{keyword}{" "}
|
||||
</Link>
|
||||
{/* {selected && (
|
||||
<Checkmark
|
||||
UNSAFE_className="mx-1"
|
||||
UNSAFE_style={{ width: "15px" }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)} */}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
@ -92,7 +92,7 @@ function Option({ item, state }: OptionProps) {
|
||||
<li
|
||||
{...optionProps}
|
||||
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" : ""}`}
|
||||
ref={ref}
|
||||
>
|
||||
|
@ -23,7 +23,7 @@ interface MasonryGalleryProps {
|
||||
[breakpoint: string]: number;
|
||||
};
|
||||
debugHue?: boolean;
|
||||
dataFn?: (image: GalleryImage) => string[] | null;
|
||||
debugRating?: boolean;
|
||||
linkState?: object;
|
||||
showPalette?: boolean;
|
||||
singleRow?: boolean;
|
||||
@ -33,7 +33,7 @@ const MasonryGallery = ({
|
||||
images: _images,
|
||||
aspectsByBreakpoint: aspectTargetsByBreakpoint,
|
||||
debugHue,
|
||||
dataFn,
|
||||
debugRating,
|
||||
linkState,
|
||||
showPalette,
|
||||
singleRow,
|
||||
@ -47,8 +47,13 @@ const MasonryGallery = ({
|
||||
[aspectTargetsByBreakpoint]
|
||||
);
|
||||
|
||||
// const { observe, currentBreakpoint } = useDimensions({
|
||||
// breakpoints,
|
||||
// });
|
||||
|
||||
const { breakpoint } = useBreakpoint(breakpoints, "xs");
|
||||
|
||||
// const breakpoint = currentBreakpoint.length ? currentBreakpoint : "xs";
|
||||
const galleryWidth = `calc(100vw - ${
|
||||
breakpoint === "xs" || breakpoint === "sm" ? "32" : "160"
|
||||
}px)`;
|
||||
@ -59,38 +64,48 @@ const MasonryGallery = ({
|
||||
) as number[];
|
||||
|
||||
const targetAspect = aspectTargetsByBreakpoint[breakpoint];
|
||||
const rows = React.useMemo(() => {
|
||||
const _rows: Row[] = [{ aspect: 0, startIndex: 0, images: 0 }];
|
||||
|
||||
for (const currentAspect of aspectRatios) {
|
||||
const currentRow = _rows[_rows.length - 1];
|
||||
const currentDiff = Math.abs(targetAspect - currentRow.aspect);
|
||||
const diffIfImageIsAddedToCurrentRow = Math.abs(
|
||||
targetAspect - (currentRow.aspect + currentAspect)
|
||||
);
|
||||
|
||||
// does adding current image to our row get us closer to our target aspect ratio?
|
||||
if (currentDiff > diffIfImageIsAddedToCurrentRow) {
|
||||
currentRow.aspect += currentAspect;
|
||||
currentRow.images += 1;
|
||||
// _rows.push(currentRow);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (singleRow) {
|
||||
break;
|
||||
}
|
||||
|
||||
// start a new row
|
||||
_rows.push({
|
||||
aspect: currentAspect,
|
||||
images: 1,
|
||||
startIndex: currentRow.startIndex + currentRow.images,
|
||||
});
|
||||
}
|
||||
|
||||
return R.indexBy(R.prop("startIndex"), _rows);
|
||||
}, [aspectRatios, targetAspect, singleRow]);
|
||||
const rows = React.useMemo(
|
||||
() =>
|
||||
R.pipe(
|
||||
R.reduce(
|
||||
(acc, currentAspect: number): Row[] => {
|
||||
const currentRow = acc.pop()!;
|
||||
const currentDiff = Math.abs(targetAspect - currentRow.aspect);
|
||||
const diffIfImageIsAddedToCurrentRow = Math.abs(
|
||||
targetAspect - (currentRow.aspect + currentAspect)
|
||||
);
|
||||
// add image to current row if it gets us closer to our target aspect ratio
|
||||
if (currentDiff > diffIfImageIsAddedToCurrentRow) {
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
aspect: currentRow.aspect + currentAspect,
|
||||
images: currentRow.images + 1,
|
||||
startIndex: currentRow.startIndex,
|
||||
},
|
||||
];
|
||||
}
|
||||
// no-op instead of starting a new row
|
||||
if (singleRow) {
|
||||
return [currentRow];
|
||||
}
|
||||
// start a new row
|
||||
return [
|
||||
...acc,
|
||||
currentRow,
|
||||
{
|
||||
aspect: currentAspect,
|
||||
images: 1,
|
||||
startIndex: currentRow.startIndex + currentRow.images,
|
||||
} as Row,
|
||||
];
|
||||
},
|
||||
[{ aspect: 0, startIndex: 0, images: 0 }] as Row[]
|
||||
),
|
||||
R.indexBy(R.prop("startIndex"))
|
||||
)(aspectRatios),
|
||||
[aspectRatios, targetAspect, singleRow]
|
||||
);
|
||||
|
||||
const sortedImageList = React.useMemo(
|
||||
() => _images.map((image) => image.base),
|
||||
@ -98,7 +113,7 @@ const MasonryGallery = ({
|
||||
);
|
||||
|
||||
const images = singleRow ? _images.slice(0, rows[0].images) : _images;
|
||||
|
||||
|
||||
let cursor = 0;
|
||||
return (
|
||||
<div
|
||||
@ -119,11 +134,9 @@ const MasonryGallery = ({
|
||||
}
|
||||
const rowAspectRatioSum = currentRow.aspect;
|
||||
const ar = getAspectRatio(image);
|
||||
let width: string;
|
||||
let height = `calc(${galleryWidth} / ${rowAspectRatioSum} ${
|
||||
showPalette ? "+ 10px" : "- 10px"
|
||||
})`;
|
||||
if (rowAspectRatioSum < targetAspect * 0.66 && !singleRow) {
|
||||
let width;
|
||||
let height = `calc(${galleryWidth} / ${rowAspectRatioSum} ${showPalette ? "+ 10px" : "- 10px"})`;
|
||||
if (rowAspectRatioSum < targetAspect * 0.66) {
|
||||
// incomplete row, render stuff at "ideal" sizes instead of filling width
|
||||
width = `calc(calc(100vw - 160px) / ${targetAspect / ar})`;
|
||||
height = "unset";
|
||||
@ -134,12 +147,10 @@ const MasonryGallery = ({
|
||||
const vibrant = getVibrant(image);
|
||||
// @ts-ignore
|
||||
const img = getImage(image);
|
||||
|
||||
const data = dataFn ? dataFn(image) : null;
|
||||
return (
|
||||
<Link
|
||||
className="border-8 border-white overflow-hidden relative"
|
||||
id={singleRow ? undefined : image.base}
|
||||
className={classNames("border-8 border-white overflow-hidden")}
|
||||
id={image.base}
|
||||
key={`${image.base}`}
|
||||
state={{
|
||||
...linkState,
|
||||
@ -159,68 +170,47 @@ const MasonryGallery = ({
|
||||
}}
|
||||
to={`/photogallery/${image.base}/`}
|
||||
>
|
||||
{data && (
|
||||
<div className="text-white z-20 absolute flex flex-col items-start">
|
||||
{data.map((dataString, i) => (
|
||||
<span
|
||||
className="bg-black/30 backdrop-blur shadow p-[2px] m-[2px] max-w-full"
|
||||
key={i}
|
||||
>
|
||||
{dataString}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{debugRating && (
|
||||
<span className="text-white z-20 absolute bg-black">
|
||||
rating: {image.fields?.imageMeta?.meta?.Rating}
|
||||
</span>
|
||||
)}
|
||||
{img && (
|
||||
<div
|
||||
className={`h-full ${
|
||||
showPalette && "grid grid-rows-[1fr_20px]"
|
||||
}`}
|
||||
>
|
||||
<div className={`h-full ${showPalette && "grid grid-rows-[1fr_20px]"}`}>
|
||||
<GatsbyImage
|
||||
alt={
|
||||
image.fields?.imageMeta?.meta?.Keywords?.length
|
||||
? `image of ${image.fields?.imageMeta?.meta?.Keywords.join(
|
||||
" and "
|
||||
)}. ${getName(image)}`
|
||||
: getName(image)
|
||||
}
|
||||
alt={getName(image)}
|
||||
className="w-full"
|
||||
image={img}
|
||||
objectFit="cover"
|
||||
/>
|
||||
{showPalette && vibrant && (
|
||||
<div className="grid grid-cols-6 flex-shrink-0 h-[20px] w-full">
|
||||
<div
|
||||
style={{
|
||||
background: `rgba(${vibrant.Vibrant?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
background: `rgb(${vibrant.LightVibrant?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
background: `rgb(${vibrant.DarkVibrant?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
style={{ background: `rgb(${vibrant.Muted?.join(",")})` }}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
background: `rgb(${vibrant.LightMuted?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
background: `rgb(${vibrant.DarkMuted?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
)}
|
||||
{ showPalette && vibrant && <div className="grid grid-cols-6 flex-shrink-0 h-[20px] w-full">
|
||||
<div
|
||||
style={{ background: `rgba(${vibrant.Vibrant?.join(",")})` }}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
background: `rgb(${vibrant.LightVibrant?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
background: `rgb(${vibrant.DarkVibrant?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
style={{ background: `rgb(${vibrant.Muted?.join(",")})` }}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
background: `rgb(${vibrant.LightMuted?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
background: `rgb(${vibrant.DarkMuted?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
</div>}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
|
@ -1,17 +1,16 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import classnames from "classnames";
|
||||
import { Link, navigate } from "gatsby";
|
||||
import { Link } from "gatsby";
|
||||
import { Popover } from "react-tiny-popover";
|
||||
import { StaticImage } from "gatsby-plugin-image";
|
||||
|
||||
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 = () => (
|
||||
<ul
|
||||
className={classnames(
|
||||
"z-30 overflow-hidden bg-vibrant-dark",
|
||||
"rounded shadow-lg border border-gray-400"
|
||||
"rounded shadow border border-vibrant-light"
|
||||
)}
|
||||
>
|
||||
<li>
|
||||
@ -88,56 +87,17 @@ interface NavProps {
|
||||
|
||||
const Nav = ({ internalLinks, className }: NavProps) => {
|
||||
const [linksMenu, setLinksMenu] = useState(false);
|
||||
const faceClicks = useRef(0);
|
||||
const faceLastClicked = useRef(0);
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={classnames(
|
||||
"my-4 flex flex-col-reverse md:flex-row",
|
||||
"justify-between",
|
||||
"items-center w-full font-sans px-4 md:px-8",
|
||||
"my-4 flex flex-col-reverse md:flex-row items-center w-full font-sans px-4 md:px-8",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-auto items-center">
|
||||
<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>
|
||||
<h2 className="text-md">Software Engineer & Photographer</h2>
|
||||
</div>
|
||||
<div className="md:flex items-baseline flex-auto">
|
||||
<h1 className="font-bold mr-2">Chuck Dries</h1>
|
||||
<h2 className="text-md">Software Engineer & Photographer</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex">
|
||||
|
@ -48,8 +48,8 @@ export function Select<T extends object>(props: AriaSelectProps<T>) {
|
||||
/>
|
||||
<button
|
||||
{...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 ${
|
||||
isFocusVisible ? "border-green-700" : "border-gray-400"
|
||||
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-black"
|
||||
} ${state.isOpen ? "bg-gray-100" : "bg-white"}`}
|
||||
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>
|
||||
);
|
||||
}
|
368
src/gatsby-types.d.ts
vendored
368
src/gatsby-types.d.ts
vendored
@ -571,7 +571,7 @@ type FileFieldsFilterInput = {
|
||||
};
|
||||
|
||||
type FileFieldsImageMeta = {
|
||||
readonly datePublished: Maybe<Scalars['Date']>;
|
||||
readonly datePublished: Maybe<Scalars['String']>;
|
||||
readonly dateTaken: Maybe<Scalars['Date']>;
|
||||
readonly dominantHue: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>;
|
||||
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 = {
|
||||
difference: InputMaybe<Scalars['String']>;
|
||||
formatString: InputMaybe<Scalars['String']>;
|
||||
@ -605,7 +597,7 @@ type FileFieldsImageMetaFieldSelector = {
|
||||
};
|
||||
|
||||
type FileFieldsImageMetaFilterInput = {
|
||||
readonly datePublished: InputMaybe<DateQueryOperatorInput>;
|
||||
readonly datePublished: InputMaybe<StringQueryOperatorInput>;
|
||||
readonly dateTaken: InputMaybe<DateQueryOperatorInput>;
|
||||
readonly dominantHue: InputMaybe<FloatQueryOperatorInput>;
|
||||
readonly meta: InputMaybe<FileFieldsImageMetaMetaFilterInput>;
|
||||
@ -1532,7 +1524,6 @@ type Query = {
|
||||
readonly allSiteFunction: SiteFunctionConnection;
|
||||
readonly allSitePage: SitePageConnection;
|
||||
readonly allSitePlugin: SitePluginConnection;
|
||||
readonly allStaticImage: StaticImageConnection;
|
||||
readonly directory: Maybe<Directory>;
|
||||
readonly file: Maybe<File>;
|
||||
readonly imageSharp: Maybe<ImageSharp>;
|
||||
@ -1541,7 +1532,6 @@ type Query = {
|
||||
readonly siteFunction: Maybe<SiteFunction>;
|
||||
readonly sitePage: Maybe<SitePage>;
|
||||
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 = {
|
||||
absolutePath: InputMaybe<StringQueryOperatorInput>;
|
||||
accessTime: InputMaybe<DateQueryOperatorInput>;
|
||||
@ -1786,46 +1768,6 @@ type Query_sitePluginArgs = {
|
||||
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 & {
|
||||
readonly buildTime: Maybe<Scalars['Date']>;
|
||||
readonly children: ReadonlyArray<Node>;
|
||||
@ -2592,312 +2534,6 @@ type SortOrderEnum =
|
||||
| 'ASC'
|
||||
| '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 = {
|
||||
readonly eq: 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.
@ -102,8 +102,8 @@ const IndexPage = ({
|
||||
objectFit={browserIsLandscape ? "cover" : "contain"}
|
||||
style={{
|
||||
height: screenHeight
|
||||
? `${screenHeight - 268}px`
|
||||
: "calc(100vh-268px)",
|
||||
? `${screenHeight - 160}px`
|
||||
: "calc(100vh-160px)",
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import * as React from "react";
|
||||
import * as R from "ramda";
|
||||
import { graphql, Link, navigate, PageProps } from "gatsby";
|
||||
import { graphql, PageProps } from "gatsby";
|
||||
import { Helmet } from "react-helmet";
|
||||
// import { Picker, Item } from "@adobe/react-spectrum";
|
||||
|
||||
import MasonryGallery from "../components/MasonryGallery";
|
||||
import KeywordsPicker from "../components/KeywordsPicker";
|
||||
@ -15,7 +16,6 @@ import Nav from "../components/Nav";
|
||||
import { Item, Select } from "../components/Select";
|
||||
import { Switch } from "../components/Switch";
|
||||
import ColorPalette from "@spectrum-icons/workflow/ColorPalette";
|
||||
import { ToggleButton } from "../components/ToggleButton";
|
||||
|
||||
const SORT_KEYS = {
|
||||
hue: ["fields", "imageMeta", "vibrantHue"],
|
||||
@ -23,48 +23,55 @@ const SORT_KEYS = {
|
||||
// hue_debug: ["fields", "imageMeta", "dominantHue", 0],
|
||||
hue_debug: ["fields", "imageMeta", "dominantHue", "0"],
|
||||
date: ["fields", "imageMeta", "dateTaken"],
|
||||
datePublished: ["fields", "imageMeta", "datePublished"],
|
||||
modified: ["fields", "imageMeta", "datePublished"],
|
||||
} as const;
|
||||
|
||||
export type GalleryImage =
|
||||
Queries.GalleryPageQueryQuery["all"]["nodes"][number];
|
||||
|
||||
function smartCompareDates(
|
||||
key: keyof typeof SORT_KEYS,
|
||||
left: GalleryImage,
|
||||
right: GalleryImage
|
||||
) {
|
||||
function smartCompareDates(key: keyof typeof SORT_KEYS, left: GalleryImage, right: GalleryImage) {
|
||||
let diff = compareDates(SORT_KEYS[key], left, right);
|
||||
console.log("🚀 ~ file: photogallery.tsx:34 ~ smartCompareDates ~ diff:", diff)
|
||||
if (diff !== 0) {
|
||||
return diff;
|
||||
}
|
||||
console.log('falling back to date')
|
||||
return compareDates(SORT_KEYS.date, left, right);
|
||||
}
|
||||
|
||||
const GalleryPage = ({
|
||||
data,
|
||||
location,
|
||||
}: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
const hash = location.hash ? location.hash.replace("#", "") : "";
|
||||
const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
const hash =
|
||||
typeof window !== "undefined" ? window.location.hash.replace("#", "") : "";
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const filterKeyword = params.get("filter");
|
||||
const sortKey = params.get("sort") ?? "rating";
|
||||
|
||||
const showDebug = Boolean(params.get("debug")?.length);
|
||||
const [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 [showPalette, setShowPalette] = React.useState(false);
|
||||
|
||||
const onKeywordPick = React.useCallback((newKeyword: string | null) => {
|
||||
if (newKeyword) {
|
||||
try {
|
||||
window.plausible("Filter Keyword", {
|
||||
props: { keyword: newKeyword },
|
||||
});
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
const 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) => {
|
||||
@ -75,65 +82,67 @@ const GalleryPage = ({
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
navigate(
|
||||
getGalleryPageUrl(
|
||||
{ sortKey: newSortKey, keyword: filterKeyword, showDebug },
|
||||
hash
|
||||
)
|
||||
// { replace: true }
|
||||
_setSortKey(newSortKey);
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
getGalleryPageUrl({ sortKey: newSortKey, keyword: filterKeyword }, hash)
|
||||
);
|
||||
},
|
||||
[filterKeyword, hash, showDebug]
|
||||
[_setSortKey, filterKeyword, hash]
|
||||
);
|
||||
|
||||
const removeHash = React.useCallback(() => {
|
||||
if (!hash.length) {
|
||||
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;
|
||||
}
|
||||
console.log('remove hash')
|
||||
navigate(
|
||||
getGalleryPageUrl({ sortKey, keyword: filterKeyword, showDebug }, ""),
|
||||
{ replace: true }
|
||||
);
|
||||
window.removeEventListener("scroll", removeHash);
|
||||
}, [hash, sortKey, filterKeyword, showDebug]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// window.addEventListener("scroll", removeHash);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", removeHash);
|
||||
};
|
||||
}, [removeHash]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// hacky but it works for now
|
||||
requestAnimationFrame(() => {
|
||||
// don't scroll into view if user got here with back button or if we just cleared it
|
||||
if (!hash.length) {
|
||||
return;
|
||||
}
|
||||
const el = document.getElementById(hash);
|
||||
if (!el) {
|
||||
console.log("⚠️failed to find hash");
|
||||
return;
|
||||
}
|
||||
console.log("scrolling into view manually", el.offsetTop);
|
||||
el.scrollIntoView({
|
||||
block: hash.startsWith("all") ? "start" : "center",
|
||||
behavior: hash.startsWith("all") ? "smooth" : "auto",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.addEventListener("scroll", removeHash);
|
||||
}, 100);
|
||||
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" || sortKey === "datePublished"
|
||||
? R.sort((node1: typeof data["all"]["nodes"][number], node2) =>
|
||||
smartCompareDates(sortKey, node1, node2)
|
||||
)
|
||||
sortKey === "date" || sortKey === "modified"
|
||||
? R.sort((node1: typeof data["all"]["nodes"][number], node2) => smartCompareDates(sortKey, node1, node2))
|
||||
: R.sort(
|
||||
// @ts-ignore
|
||||
R.descend(R.path<GalleryImage>(SORT_KEYS[sortKey]))
|
||||
@ -162,50 +171,8 @@ const GalleryPage = ({
|
||||
}, [data, sortKey, filterKeyword]);
|
||||
|
||||
const recents = React.useMemo(() => {
|
||||
return R.sort(
|
||||
(left, right) => smartCompareDates("datePublished", left, right),
|
||||
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 R.sort((left, right) => smartCompareDates('modified', left, right), data.recents.nodes)
|
||||
}, [data, 'hi'])
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -238,50 +205,31 @@ const GalleryPage = ({
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="gradient pb-6">
|
||||
<div className="px-4 md:px-8 flex items-baseline">
|
||||
<h3 className="mx-2 font-bold" id="recently">
|
||||
Recently published
|
||||
</h3>
|
||||
{sortKey !== "datePublished" && (
|
||||
<Link
|
||||
className="underline cursor-pointer text-gray-500"
|
||||
to="?sort=datePublished#all"
|
||||
>
|
||||
show more
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<MasonryGallery
|
||||
aspectsByBreakpoint={{
|
||||
xs: 3,
|
||||
sm: 3,
|
||||
md: 4,
|
||||
lg: 4,
|
||||
xl: 5,
|
||||
"2xl": 6,
|
||||
"3xl": 8,
|
||||
}}
|
||||
images={recents}
|
||||
linkState={{
|
||||
sortKey: 'datePublished',
|
||||
filterKeyword,
|
||||
}}
|
||||
singleRow
|
||||
/>
|
||||
<div className="px-4 md:px-8">
|
||||
<h3 id="recently" className="mx-2 font-bold">
|
||||
Recently published
|
||||
</h3>
|
||||
</div>
|
||||
<div className="px-4 md:px-8 mt-2 pt-2">
|
||||
<h3 className="mx-2 font-bold" id="all">
|
||||
<MasonryGallery
|
||||
aspectsByBreakpoint={{
|
||||
xs: 2,
|
||||
sm: 2,
|
||||
md: 3,
|
||||
lg: 4,
|
||||
xl: 5,
|
||||
"2xl": 6.1,
|
||||
"3xl": 8,
|
||||
}}
|
||||
images={recents}
|
||||
singleRow
|
||||
/>
|
||||
<div className="px-4 md:px-8 mt-4 pt-2 border-t">
|
||||
<h3 id="all" className="mx-2 font-bold">
|
||||
All images
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row lg:items-end justify-between px-4 md:px-8 sm:mx-auto">
|
||||
<KeywordsPicker
|
||||
getHref={(val, selected) =>
|
||||
selected
|
||||
? getGalleryPageUrl({ keyword: null, sortKey, showDebug }, hash)
|
||||
: getGalleryPageUrl({ keyword: val, sortKey, showDebug }, hash)
|
||||
}
|
||||
keywords={[
|
||||
"Boyce Thompson Arboretum",
|
||||
"winter",
|
||||
@ -291,31 +239,18 @@ const GalleryPage = ({
|
||||
"landscape",
|
||||
"flowers",
|
||||
"product",
|
||||
// "waterfall",
|
||||
// "fireworks",
|
||||
// "panoramic",
|
||||
"waterfall",
|
||||
"fireworks",
|
||||
"panoramic",
|
||||
"Portland Japanese Garden",
|
||||
// "shoot the light",
|
||||
// "sunset",
|
||||
]}
|
||||
onPick={onKeywordPick}
|
||||
onChange={setKeyword}
|
||||
value={filterKeyword}
|
||||
/>
|
||||
<div className="my-2 mx-2 flex flex-row items-end">
|
||||
{showDebug && (
|
||||
<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">
|
||||
<div className="m-2 flex flex-row items-end">
|
||||
<div className="border border-black rounded mr-2">
|
||||
<Switch
|
||||
isSelected={showPalette}
|
||||
onChange={(val) => setShowPalette(val)}
|
||||
@ -330,12 +265,12 @@ const GalleryPage = ({
|
||||
</div>
|
||||
<Select
|
||||
label="Sort by..."
|
||||
// @ts-expect-error React.key, but string is more convenient for the state
|
||||
// @ts-ignore
|
||||
onSelectionChange={setSortKey}
|
||||
selectedKey={sortKey}
|
||||
>
|
||||
<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="hue">Hue</Item>
|
||||
</Select>
|
||||
@ -352,8 +287,8 @@ const GalleryPage = ({
|
||||
"2xl": 6.1,
|
||||
"3xl": 8,
|
||||
}}
|
||||
dataFn={dataFn}
|
||||
debugHue={sortKey === "hue_debug"}
|
||||
debugRating={sortKey === "rating" && showDebug}
|
||||
images={images}
|
||||
linkState={{
|
||||
sortKey,
|
||||
@ -370,7 +305,7 @@ export const query = graphql`
|
||||
recents: allFile(
|
||||
filter: { sourceInstanceName: { eq: "gallery" } }
|
||||
sort: { fields: { imageMeta: { datePublished: DESC } } }
|
||||
limit: 10
|
||||
limit: 7
|
||||
) {
|
||||
...GalleryImageFile
|
||||
}
|
||||
|
@ -9,9 +9,6 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:root {
|
||||
/* scroll-behavior: smooth; */
|
||||
}
|
||||
/* .hero * {
|
||||
transition: color .2s, background-color .2s;
|
||||
} */
|
||||
@ -86,15 +83,27 @@
|
||||
.gradient {
|
||||
background-image: linear-gradient(
|
||||
180deg,
|
||||
hsla(0deg, 0%, 0%, 0%) 95%,
|
||||
hsla(0deg, 0%, 0%, 10%) 100%
|
||||
hsl(31deg 24% 44%) 0%,
|
||||
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 {
|
||||
@apply bg-gray-100;
|
||||
overflow: auto;
|
||||
/* @apply bg-black; */
|
||||
/* @apply text-white; */
|
||||
}
|
||||
|
42
src/utils.ts
42
src/utils.ts
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useRef } from "react";
|
||||
import React from "react";
|
||||
|
||||
import { pathOr } from "ramda";
|
||||
// import kebabCase from 'lodash/kebabCase';
|
||||
@ -101,12 +101,11 @@ export const getShutterFractionFromExposureTime = (exposureTime: number) => {
|
||||
|
||||
interface galleryPageUrlProps {
|
||||
keyword: string | null;
|
||||
sortKey: string | null;
|
||||
showDebug: boolean;
|
||||
sortKey: string;
|
||||
}
|
||||
|
||||
export const getGalleryPageUrl = (
|
||||
{ keyword, sortKey, showDebug }: galleryPageUrlProps,
|
||||
{ keyword, sortKey }: galleryPageUrlProps,
|
||||
hash: string
|
||||
) => {
|
||||
const url = new URL(
|
||||
@ -124,15 +123,12 @@ export const getGalleryPageUrl = (
|
||||
}
|
||||
}
|
||||
if (sortKey !== undefined) {
|
||||
if (sortKey === "rating" || sortKey === null) {
|
||||
if (sortKey === "rating") {
|
||||
url.searchParams.delete("sort");
|
||||
} else {
|
||||
url.searchParams.set("sort", sortKey);
|
||||
}
|
||||
}
|
||||
if (showDebug) {
|
||||
url.searchParams.set("debug", "true");
|
||||
}
|
||||
if (hash) {
|
||||
url.hash = hash;
|
||||
}
|
||||
@ -145,34 +141,8 @@ export function compareDates<T>(
|
||||
right: T
|
||||
): number {
|
||||
// why tf do my dates have newlines in them?!?!
|
||||
const date1 = new Date(pathOr("", date_path, left).replace(/\s/g, ""));
|
||||
const date2 = new Date(pathOr("", date_path, right).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 diff = -1 * (date1.getTime() - date2.getTime());
|
||||
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]
|
||||
);
|
||||
};
|
18
yarn.lock
18
yarn.lock
@ -4560,22 +4560,6 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 4.14.170
|
||||
resolution: "@types/lodash@npm:4.14.170"
|
||||
@ -6480,7 +6464,6 @@ __metadata:
|
||||
"@react-spectrum/provider": ^3.6.0
|
||||
"@spectrum-icons/workflow": ^4.0.4
|
||||
"@types/chroma-js": ^2.1.4
|
||||
"@types/lodash.debounce": ^4.0.7
|
||||
"@types/node": ^18.8.3
|
||||
"@types/ramda": ^0.28.15
|
||||
"@types/react": ^18.0.21
|
||||
@ -6512,7 +6495,6 @@ __metadata:
|
||||
gatsby-source-filesystem: ^5.0.0
|
||||
gatsby-transformer-sharp: ^5.0.0
|
||||
kebab-case: ^1.0.1
|
||||
lodash.debounce: ^4.0.8
|
||||
node-iptc: ^1.0.5
|
||||
node-vibrant: 3.1.6
|
||||
postcss: ^8.4.19
|
||||
|
Loading…
x
Reference in New Issue
Block a user