Merge branch 'recent-images' into main
This commit is contained in:
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.
+34
-4
@@ -10,6 +10,10 @@ import sharp from "sharp";
|
||||
import { Palette } from "node-vibrant/lib/color";
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
import util from "node:util";
|
||||
import { exec as _exec } from "child_process";
|
||||
const exec = util.promisify(_exec);
|
||||
|
||||
// const path = require("path");
|
||||
// const Vibrant = require("node-vibrant");
|
||||
// const chroma = require("chroma-js");
|
||||
@@ -114,7 +118,8 @@ function transformMetaToNodeData(
|
||||
metaData: Record<string, unknown>,
|
||||
vibrantData: Palette,
|
||||
imagePath: string,
|
||||
{ r, g, b }: { r: number; b: number; g: number }
|
||||
{ r, g, b }: { r: number; b: number; g: number },
|
||||
datePublished: string
|
||||
) {
|
||||
const vibrant = vibrantData ? processColors(vibrantData, imagePath) : null;
|
||||
const vibrantHue = vibrantData.Vibrant!.getHsl()[0] * 360;
|
||||
@@ -131,6 +136,7 @@ function transformMetaToNodeData(
|
||||
}
|
||||
return {
|
||||
dateTaken: metaData.DateTimeOriginal,
|
||||
datePublished,
|
||||
meta: {
|
||||
Make: metaData.Make,
|
||||
Model: metaData.Model,
|
||||
@@ -139,6 +145,7 @@ function transformMetaToNodeData(
|
||||
ISO: metaData.ISO,
|
||||
DateTimeOriginal: metaData.DateTimeOriginal,
|
||||
CreateDate: metaData.CreateDate,
|
||||
ModifyDate: metaData.ModifyDate,
|
||||
ShutterSpeedValue: metaData.ShutterSpeedValue,
|
||||
ApertureValue: metaData.ApertureValue,
|
||||
FocalLength: metaData.FocalLength,
|
||||
@@ -173,13 +180,34 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async function ({
|
||||
const { createNodeField } = actions;
|
||||
|
||||
if (node.internal.type === "File" && node.sourceInstanceName === "gallery") {
|
||||
const metaData = await exifr.parse(node.absolutePath as string, {
|
||||
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);
|
||||
}
|
||||
|
||||
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 sharpImage = sharp(node.absolutePath as string);
|
||||
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 { dominant } = await sharpImage.stats();
|
||||
const resizedImage = await sharpImage
|
||||
.resize({
|
||||
@@ -200,7 +228,9 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async function ({
|
||||
metaData,
|
||||
vibrantData,
|
||||
node.absolutePath as string,
|
||||
dominant
|
||||
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()
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
a = {
|
||||
ApplicationRecordVersion: '\x00\x04',
|
||||
DateCreated: '2015-10-18T17:59:38',
|
||||
TimeCreated: '175938',
|
||||
DigitalCreationDate: '20151018',
|
||||
DigitalCreationTime: '175938',
|
||||
format: 'image/jpeg',
|
||||
CreatorTool: 'Adobe Lightroom 5.4 (Macintosh)',
|
||||
ModifyDate: '2022-07-09T21:16:39.000Z',
|
||||
CreateDate: '2015-10-19T00:59:38.000Z',
|
||||
MetadataDate: '2022-07-09T14:16:39-07:00',
|
||||
Rating: 5,
|
||||
LensInfo: [ 50, 50, 1.8, 1.8 ],
|
||||
Lens: 'DT 50mm F1.8 SAM',
|
||||
DocumentID: 'xmp.did:27795bf1-8bb2-49a0-807a-cf6150bd505b',
|
||||
OriginalDocumentID: 'CE2B8F240393ADFDF9F092CF395C6632',
|
||||
InstanceID: 'xmp.iid:27795bf1-8bb2-49a0-807a-cf6150bd505b',
|
||||
History: [
|
||||
{ action: 'derived', parameters: 'saved to new location' },
|
||||
{
|
||||
action: 'saved',
|
||||
instanceID: 'xmp.iid:27795bf1-8bb2-49a0-807a-cf6150bd505b',
|
||||
when: '2022-07-09T14:16:39-07:00',
|
||||
softwareAgent: 'Adobe Lightroom 5.4 (Macintosh)',
|
||||
changed: '/'
|
||||
}
|
||||
],
|
||||
DerivedFrom: {
|
||||
documentID: 'CE2B8F240393ADFDF9F092CF395C6632',
|
||||
originalDocumentID: 'CE2B8F240393ADFDF9F092CF395C6632'
|
||||
},
|
||||
good: true,
|
||||
Version: 14.4,
|
||||
ProcessVersion: 6.7,
|
||||
WhiteBalance: 'Manual',
|
||||
IncrementalTemperature: -11,
|
||||
IncrementalTint: 23,
|
||||
Exposure2012: 0,
|
||||
Contrast2012: 6,
|
||||
Highlights2012: 0,
|
||||
Shadows2012: 0,
|
||||
Whites2012: 35,
|
||||
Blacks2012: -22,
|
||||
Texture: 0,
|
||||
Clarity2012: 0,
|
||||
Dehaze: 0,
|
||||
Vibrance: 0,
|
||||
Saturation: 'Normal',
|
||||
ParametricShadows: 0,
|
||||
ParametricDarks: 0,
|
||||
ParametricLights: 0,
|
||||
ParametricHighlights: 0,
|
||||
ParametricShadowSplit: 25,
|
||||
ParametricMidtoneSplit: 50,
|
||||
ParametricHighlightSplit: 75,
|
||||
Sharpness: 'Normal',
|
||||
LuminanceSmoothing: 0,
|
||||
ColorNoiseReduction: 0,
|
||||
HueAdjustmentRed: 0,
|
||||
HueAdjustmentOrange: 0,
|
||||
HueAdjustmentYellow: 0,
|
||||
HueAdjustmentGreen: 0,
|
||||
HueAdjustmentAqua: 0,
|
||||
HueAdjustmentBlue: 0,
|
||||
HueAdjustmentPurple: 0,
|
||||
HueAdjustmentMagenta: 0,
|
||||
SaturationAdjustmentRed: 0,
|
||||
SaturationAdjustmentOrange: 0,
|
||||
SaturationAdjustmentYellow: 0,
|
||||
SaturationAdjustmentGreen: 0,
|
||||
SaturationAdjustmentAqua: 0,
|
||||
SaturationAdjustmentBlue: 0,
|
||||
SaturationAdjustmentPurple: 0,
|
||||
SaturationAdjustmentMagenta: 0,
|
||||
LuminanceAdjustmentRed: 0,
|
||||
LuminanceAdjustmentOrange: 0,
|
||||
LuminanceAdjustmentYellow: 0,
|
||||
LuminanceAdjustmentGreen: 0,
|
||||
LuminanceAdjustmentAqua: 0,
|
||||
LuminanceAdjustmentBlue: 0,
|
||||
LuminanceAdjustmentPurple: 0,
|
||||
LuminanceAdjustmentMagenta: 0,
|
||||
SplitToningShadowHue: 0,
|
||||
SplitToningShadowSaturation: 0,
|
||||
SplitToningHighlightHue: 0,
|
||||
SplitToningHighlightSaturation: 0,
|
||||
SplitToningBalance: 0,
|
||||
ColorGradeMidtoneHue: 0,
|
||||
ColorGradeMidtoneSat: 0,
|
||||
ColorGradeShadowLum: 0,
|
||||
ColorGradeMidtoneLum: 0,
|
||||
ColorGradeHighlightLum: 0,
|
||||
ColorGradeBlending: 50,
|
||||
ColorGradeGlobalHue: 0,
|
||||
ColorGradeGlobalSat: 0,
|
||||
ColorGradeGlobalLum: 0,
|
||||
AutoLateralCA: 0,
|
||||
LensProfileEnable: 0,
|
||||
LensManualDistortionAmount: 0,
|
||||
VignetteAmount: 0,
|
||||
DefringePurpleAmount: 0,
|
||||
DefringePurpleHueLo: 30,
|
||||
DefringePurpleHueHi: 70,
|
||||
DefringeGreenAmount: 0,
|
||||
DefringeGreenHueLo: 40,
|
||||
DefringeGreenHueHi: 60,
|
||||
PerspectiveUpright: 0,
|
||||
PerspectiveVertical: 0,
|
||||
PerspectiveHorizontal: 0,
|
||||
PerspectiveRotate: 0,
|
||||
PerspectiveAspect: 0,
|
||||
PerspectiveScale: 100,
|
||||
PerspectiveX: 0,
|
||||
PerspectiveY: 0,
|
||||
GrainAmount: 0,
|
||||
PostCropVignetteAmount: 0,
|
||||
ShadowTint: 0,
|
||||
RedHue: 0,
|
||||
RedSaturation: 0,
|
||||
GreenHue: 0,
|
||||
GreenSaturation: 0,
|
||||
BlueHue: 0,
|
||||
BlueSaturation: 0,
|
||||
ConvertToGrayscale: false,
|
||||
OverrideLookVignette: false,
|
||||
ToneCurveName2012: 'Linear',
|
||||
CameraProfile: 'Embedded',
|
||||
CameraProfileDigest: '54650A341B5B5CCAE8442D0B43A92BCE',
|
||||
HasSettings: true,
|
||||
CropTop: 0,
|
||||
CropLeft: 0,
|
||||
CropBottom: 1,
|
||||
CropRight: 1,
|
||||
CropAngle: 0,
|
||||
CropConstrainToWarp: 0,
|
||||
HasCrop: false,
|
||||
AlreadyApplied: true,
|
||||
ToneCurvePV2012: [ '0, 0', '255, 255' ],
|
||||
ToneCurvePV2012Red: [ '0, 0', '255, 255' ],
|
||||
ToneCurvePV2012Green: [ '0, 0', '255, 255' ],
|
||||
ToneCurvePV2012Blue: [ '0, 0', '255, 255' ],
|
||||
Make: 'SONY',
|
||||
Model: 'SLT-A55V',
|
||||
XResolution: 240,
|
||||
YResolution: 240,
|
||||
ResolutionUnit: 'inches',
|
||||
Software: 'Adobe Lightroom 5.4 (Macintosh)',
|
||||
ExposureTime: 0.01,
|
||||
FNumber: 1.8,
|
||||
ExposureProgram: 'Manual',
|
||||
ISO: 200,
|
||||
SensitivityType: 2,
|
||||
RecommendedExposureIndex: 200,
|
||||
ExifVersion: '2.3.1',
|
||||
DateTimeOriginal: '2015-10-19T00:59:38.000Z',
|
||||
OffsetTime: '-07:00',
|
||||
ShutterSpeedValue: 6.643856,
|
||||
ApertureValue: 1.695994,
|
||||
BrightnessValue: 3,
|
||||
ExposureCompensation: 0,
|
||||
MaxApertureValue: 1.7,
|
||||
MeteringMode: 'Pattern',
|
||||
LightSource: 'Cloudy weather',
|
||||
Flash: 'Flash did not fire, compulsory flash mode',
|
||||
FocalLength: 50,
|
||||
ColorSpace: 1,
|
||||
FileSource: 'Digital Camera',
|
||||
SceneType: 'Directly photographed',
|
||||
CustomRendered: 'Normal',
|
||||
ExposureMode: 'Manual',
|
||||
FocalLengthIn35mmFormat: 75,
|
||||
SceneCaptureType: 'Standard',
|
||||
Contrast: 'Normal',
|
||||
LensModel: 'DT 50mm F1.8 SAM',
|
||||
GPSVersionID: '2.2.0.0',
|
||||
GPSLatitudeRef: 'N',
|
||||
GPSLatitude: [ 33, 25.7121, 0 ],
|
||||
GPSLongitudeRef: 'W',
|
||||
GPSLongitude: [ 111, 55.57122, 0 ],
|
||||
GPSAltitude: 354.7,
|
||||
GPSTimeStamp: '19:2:7',
|
||||
GPSStatus: 'V',
|
||||
GPSMeasureMode: '3',
|
||||
GPSSpeedRef: 'K',
|
||||
GPSSpeed: 33.8,
|
||||
GPSTrackRef: 'T',
|
||||
GPSTrack: 0.36,
|
||||
GPSMapDatum: 'WGS-84',
|
||||
GPSDateStamp: '2015:10:09',
|
||||
GPSDifferential: 0,
|
||||
latitude: 33.428535,
|
||||
longitude: -111.926187
|
||||
}
|
||||
@@ -63,6 +63,12 @@ const GalleryImage = ({ data, location: { state } }) => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
requestAnimationFrame(() => {
|
||||
window.scrollTo(0, 180);
|
||||
});
|
||||
}, [image.base]);
|
||||
|
||||
const nextIndex =
|
||||
sortedImageList && currentIndex < sortedImageList.length
|
||||
? currentIndex + 1
|
||||
@@ -135,7 +141,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();
|
||||
@@ -145,7 +151,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 ? "250px" : "100px";
|
||||
const verticalPad = ar > 1 ? "180px" : "20px";
|
||||
|
||||
const shutterSpeed = React.useMemo(
|
||||
() =>
|
||||
|
||||
@@ -1,44 +1,37 @@
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
import Checkmark from "@spectrum-icons/workflow/Checkmark";
|
||||
import { Link } from "gatsby";
|
||||
|
||||
interface KeywordsPickerProps {
|
||||
keywords: string[];
|
||||
value: string | null;
|
||||
onChange: (val: string | null) => void;
|
||||
getHref: (value: string | null) => string;
|
||||
onPick: (value: string | null) => void;
|
||||
}
|
||||
const KeywordsPicker = ({ keywords, value, onChange }: KeywordsPickerProps) => {
|
||||
const KeywordsPicker = ({ keywords, value, getHref, onPick }: 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}>
|
||||
<button
|
||||
<Link
|
||||
className={classNames(
|
||||
`py-[5px] px-3 rounded-full text-sm`,
|
||||
`text-black border border-black`,
|
||||
`py-[5px] px-3 rounded-full text-sm block`,
|
||||
`text-black border border-gray-400`,
|
||||
selected
|
||||
? "bg-transparentblack font-bold"
|
||||
? "bg-black/10 font-bold"
|
||||
: `bg-white
|
||||
hover:bg-transparentblack`
|
||||
hover:bg-black/10`
|
||||
)}
|
||||
onClick={() => (selected ? onChange(null) : onChange(keyword))}
|
||||
type="button"
|
||||
onClick={() => onPick(keyword)}
|
||||
replace={false}
|
||||
to={getHref(keyword)}
|
||||
>
|
||||
{keyword}{" "}
|
||||
{/* {selected && (
|
||||
<Checkmark
|
||||
UNSAFE_className="mx-1"
|
||||
UNSAFE_style={{ width: "15px" }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)} */}
|
||||
</button>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -91,8 +91,8 @@ function Option({ item, state }: OptionProps) {
|
||||
return (
|
||||
<li
|
||||
{...optionProps}
|
||||
className={`p-3 outline-none cursor-default flex items-center justify-between ${text} text-sm ${
|
||||
isFocused ? "bg-transparentblack" : ""
|
||||
className={`p-2 outline-none cursor-default flex items-center justify-between ${text} text-sm ${
|
||||
isFocused ? "bg-black/10" : ""
|
||||
} ${isSelected ? "font-bold" : ""}`}
|
||||
ref={ref}
|
||||
>
|
||||
|
||||
@@ -18,23 +18,25 @@ interface Row {
|
||||
}
|
||||
|
||||
interface MasonryGalleryProps {
|
||||
images: GalleryImage[];
|
||||
images: readonly GalleryImage[];
|
||||
aspectsByBreakpoint: {
|
||||
[breakpoint: string]: number;
|
||||
};
|
||||
debugHue?: boolean;
|
||||
debugRating?: boolean;
|
||||
dataFn?: (image: GalleryImage) => (string | null);
|
||||
linkState?: object;
|
||||
showPalette?: boolean;
|
||||
singleRow?: boolean;
|
||||
}
|
||||
|
||||
const MasonryGallery = ({
|
||||
images,
|
||||
images: _images,
|
||||
aspectsByBreakpoint: aspectTargetsByBreakpoint,
|
||||
debugHue,
|
||||
debugRating,
|
||||
dataFn,
|
||||
linkState,
|
||||
showPalette,
|
||||
singleRow,
|
||||
}: MasonryGalleryProps) => {
|
||||
const [isClient, setIsClient] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
@@ -50,6 +52,7 @@ const MasonryGallery = ({
|
||||
// });
|
||||
|
||||
const { breakpoint } = useBreakpoint(breakpoints, "xs");
|
||||
console.log("🚀 ~ file: MasonryGallery.tsx:55 ~ breakpoint:", breakpoint)
|
||||
|
||||
// const breakpoint = currentBreakpoint.length ? currentBreakpoint : "xs";
|
||||
const galleryWidth = `calc(100vw - ${
|
||||
@@ -57,54 +60,51 @@ const MasonryGallery = ({
|
||||
}px)`;
|
||||
|
||||
const aspectRatios = React.useMemo(
|
||||
() => R.map(getAspectRatio, images).filter(Boolean),
|
||||
[images]
|
||||
() => R.map(getAspectRatio, _images).filter(Boolean),
|
||||
[_images]
|
||||
) as number[];
|
||||
|
||||
const targetAspect = aspectTargetsByBreakpoint[breakpoint];
|
||||
const rows = React.useMemo(
|
||||
() =>
|
||||
R.pipe(
|
||||
R.reduce(
|
||||
(acc, currentAspect: number): Row[] => {
|
||||
const currentRow = acc.pop()!;
|
||||
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)
|
||||
);
|
||||
// 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) {
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
aspect: currentRow.aspect + currentAspect,
|
||||
images: currentRow.images + 1,
|
||||
startIndex: currentRow.startIndex,
|
||||
},
|
||||
];
|
||||
currentRow.aspect += currentAspect;
|
||||
currentRow.images += 1
|
||||
// _rows.push(currentRow);
|
||||
continue;
|
||||
}
|
||||
return [
|
||||
...acc,
|
||||
currentRow,
|
||||
{
|
||||
|
||||
if (singleRow) {
|
||||
break;
|
||||
}
|
||||
|
||||
// start a new row
|
||||
_rows.push({
|
||||
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]
|
||||
);
|
||||
startIndex: currentRow.startIndex + currentRow.images
|
||||
})
|
||||
}
|
||||
|
||||
return R.indexBy(R.prop("startIndex"), _rows);
|
||||
}, [aspectRatios, targetAspect, singleRow]);
|
||||
|
||||
const sortedImageList = React.useMemo(
|
||||
() => images.map((image) => image.base),
|
||||
[images]
|
||||
() => _images.map((image) => image.base),
|
||||
[_images]
|
||||
);
|
||||
|
||||
const images = singleRow ? _images.slice(0, rows[0].images) : _images;
|
||||
|
||||
let cursor = 0;
|
||||
return (
|
||||
<div
|
||||
@@ -126,8 +126,10 @@ const MasonryGallery = ({
|
||||
const rowAspectRatioSum = currentRow.aspect;
|
||||
const ar = getAspectRatio(image);
|
||||
let width;
|
||||
let height = `calc(${galleryWidth} / ${rowAspectRatioSum} ${showPalette ? "+ 10px" : "- 10px"})`;
|
||||
if (rowAspectRatioSum < targetAspect * 0.66) {
|
||||
let height = `calc(${galleryWidth} / ${rowAspectRatioSum} ${
|
||||
showPalette ? "+ 10px" : "- 10px"
|
||||
})`;
|
||||
if (rowAspectRatioSum < targetAspect * 0.66 && !singleRow) {
|
||||
// incomplete row, render stuff at "ideal" sizes instead of filling width
|
||||
width = `calc(calc(100vw - 160px) / ${targetAspect / ar})`;
|
||||
height = "unset";
|
||||
@@ -138,6 +140,8 @@ const MasonryGallery = ({
|
||||
const vibrant = getVibrant(image);
|
||||
// @ts-ignore
|
||||
const img = getImage(image);
|
||||
|
||||
const data = dataFn ? dataFn(image) : null;
|
||||
return (
|
||||
<Link
|
||||
className={classNames("border-8 border-white overflow-hidden")}
|
||||
@@ -161,22 +165,27 @@ const MasonryGallery = ({
|
||||
}}
|
||||
to={`/photogallery/${image.base}/`}
|
||||
>
|
||||
{debugRating && (
|
||||
<span className="text-white z-20 absolute bg-black">
|
||||
rating: {image.fields?.imageMeta?.meta?.Rating}
|
||||
</span>
|
||||
)}
|
||||
{data && <span className="text-white z-20 absolute bg-black">
|
||||
{data}
|
||||
</span>}
|
||||
{img && (
|
||||
<div className={`h-full ${showPalette && "grid grid-rows-[1fr_20px]"}`}>
|
||||
<div
|
||||
className={`h-full ${
|
||||
showPalette && "grid grid-rows-[1fr_20px]"
|
||||
}`}
|
||||
>
|
||||
<GatsbyImage
|
||||
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">
|
||||
{showPalette && vibrant && (
|
||||
<div className="grid grid-cols-6 flex-shrink-0 h-[20px] w-full">
|
||||
<div
|
||||
style={{ background: `rgba(${vibrant.Vibrant?.join(",")})` }}
|
||||
style={{
|
||||
background: `rgba(${vibrant.Vibrant?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
@@ -201,7 +210,8 @@ const MasonryGallery = ({
|
||||
background: `rgb(${vibrant.DarkMuted?.join(",")})`,
|
||||
}}
|
||||
></div>
|
||||
</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
+28
-5
@@ -2,15 +2,15 @@ import React, { useState } from "react";
|
||||
import classnames from "classnames";
|
||||
import { Link } from "gatsby";
|
||||
import { Popover } from "react-tiny-popover";
|
||||
import { StaticImage } from "gatsby-plugin-image";
|
||||
|
||||
const navClasses =
|
||||
"hover:underline hover:bg-transparentblack block p-3 text-black";
|
||||
const navClasses = "hover:underline hover:bg-black/10 block p-3 text-black flex-shrink-0 whitespace-nowrap";
|
||||
|
||||
const ExternalLinks = () => (
|
||||
<ul
|
||||
className={classnames(
|
||||
"z-30 overflow-hidden bg-vibrant-dark",
|
||||
"rounded shadow border border-vibrant-light"
|
||||
"rounded shadow-lg border border-gray-400"
|
||||
)}
|
||||
>
|
||||
<li>
|
||||
@@ -91,14 +91,37 @@ const Nav = ({ internalLinks, className }: NavProps) => {
|
||||
return (
|
||||
<nav
|
||||
className={classnames(
|
||||
"my-4 flex flex-col-reverse md:flex-row items-center w-full font-sans px-4 md:px-8",
|
||||
"my-4 flex flex-col-reverse md:flex-row",
|
||||
"justify-between",
|
||||
"items-center w-full font-sans px-4 md:px-8",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="md:flex items-baseline flex-auto">
|
||||
<div className="flex flex-auto items-center">
|
||||
<div
|
||||
className={classnames(
|
||||
"h-[120px] w-[120px] mr-4 my-5 flex-shrink-0",
|
||||
// "ml-[-130px]",
|
||||
// "rounded-full overflow-hidden relative"
|
||||
)}
|
||||
>
|
||||
<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>
|
||||
|
||||
<div className="flex">
|
||||
<ul 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-transparentblack ${
|
||||
isFocusVisible ? "border-green-500" : "border-black"
|
||||
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"
|
||||
} ${state.isOpen ? "bg-gray-100" : "bg-white"}`}
|
||||
ref={ref}
|
||||
>
|
||||
|
||||
Vendored
+383
-1
@@ -571,6 +571,7 @@ type FileFieldsFilterInput = {
|
||||
};
|
||||
|
||||
type FileFieldsImageMeta = {
|
||||
readonly datePublished: Maybe<Scalars['Date']>;
|
||||
readonly dateTaken: Maybe<Scalars['Date']>;
|
||||
readonly dominantHue: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>;
|
||||
readonly meta: Maybe<FileFieldsImageMetaMeta>;
|
||||
@@ -579,6 +580,14 @@ 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']>;
|
||||
@@ -587,6 +596,7 @@ type FileFieldsImageMeta_dateTakenArgs = {
|
||||
};
|
||||
|
||||
type FileFieldsImageMetaFieldSelector = {
|
||||
readonly datePublished: InputMaybe<FieldSelectorEnum>;
|
||||
readonly dateTaken: InputMaybe<FieldSelectorEnum>;
|
||||
readonly dominantHue: InputMaybe<FieldSelectorEnum>;
|
||||
readonly meta: InputMaybe<FileFieldsImageMetaMetaFieldSelector>;
|
||||
@@ -595,6 +605,7 @@ type FileFieldsImageMetaFieldSelector = {
|
||||
};
|
||||
|
||||
type FileFieldsImageMetaFilterInput = {
|
||||
readonly datePublished: InputMaybe<DateQueryOperatorInput>;
|
||||
readonly dateTaken: InputMaybe<DateQueryOperatorInput>;
|
||||
readonly dominantHue: InputMaybe<FloatQueryOperatorInput>;
|
||||
readonly meta: InputMaybe<FileFieldsImageMetaMetaFilterInput>;
|
||||
@@ -617,6 +628,7 @@ type FileFieldsImageMetaMeta = {
|
||||
readonly Location: Maybe<Scalars['String']>;
|
||||
readonly Make: Maybe<Scalars['String']>;
|
||||
readonly Model: Maybe<Scalars['String']>;
|
||||
readonly ModifyDate: Maybe<Scalars['Date']>;
|
||||
readonly ObjectName: Maybe<Scalars['String']>;
|
||||
readonly Rating: Maybe<Scalars['Int']>;
|
||||
readonly ShutterSpeedValue: Maybe<Scalars['Float']>;
|
||||
@@ -639,6 +651,14 @@ type FileFieldsImageMetaMeta_DateTimeOriginalArgs = {
|
||||
locale: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
type FileFieldsImageMetaMeta_ModifyDateArgs = {
|
||||
difference: InputMaybe<Scalars['String']>;
|
||||
formatString: InputMaybe<Scalars['String']>;
|
||||
fromNow: InputMaybe<Scalars['Boolean']>;
|
||||
locale: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
type FileFieldsImageMetaMetaFieldSelector = {
|
||||
readonly ApertureValue: InputMaybe<FieldSelectorEnum>;
|
||||
readonly Caption: InputMaybe<FieldSelectorEnum>;
|
||||
@@ -654,6 +674,7 @@ type FileFieldsImageMetaMetaFieldSelector = {
|
||||
readonly Location: InputMaybe<FieldSelectorEnum>;
|
||||
readonly Make: InputMaybe<FieldSelectorEnum>;
|
||||
readonly Model: InputMaybe<FieldSelectorEnum>;
|
||||
readonly ModifyDate: InputMaybe<FieldSelectorEnum>;
|
||||
readonly ObjectName: InputMaybe<FieldSelectorEnum>;
|
||||
readonly Rating: InputMaybe<FieldSelectorEnum>;
|
||||
readonly ShutterSpeedValue: InputMaybe<FieldSelectorEnum>;
|
||||
@@ -675,6 +696,7 @@ type FileFieldsImageMetaMetaFilterInput = {
|
||||
readonly Location: InputMaybe<StringQueryOperatorInput>;
|
||||
readonly Make: InputMaybe<StringQueryOperatorInput>;
|
||||
readonly Model: InputMaybe<StringQueryOperatorInput>;
|
||||
readonly ModifyDate: InputMaybe<DateQueryOperatorInput>;
|
||||
readonly ObjectName: InputMaybe<StringQueryOperatorInput>;
|
||||
readonly Rating: InputMaybe<IntQueryOperatorInput>;
|
||||
readonly ShutterSpeedValue: InputMaybe<FloatQueryOperatorInput>;
|
||||
@@ -696,6 +718,7 @@ type FileFieldsImageMetaMetaSortInput = {
|
||||
readonly Location: InputMaybe<SortOrderEnum>;
|
||||
readonly Make: InputMaybe<SortOrderEnum>;
|
||||
readonly Model: InputMaybe<SortOrderEnum>;
|
||||
readonly ModifyDate: InputMaybe<SortOrderEnum>;
|
||||
readonly ObjectName: InputMaybe<SortOrderEnum>;
|
||||
readonly Rating: InputMaybe<SortOrderEnum>;
|
||||
readonly ShutterSpeedValue: InputMaybe<SortOrderEnum>;
|
||||
@@ -703,6 +726,7 @@ type FileFieldsImageMetaMetaSortInput = {
|
||||
};
|
||||
|
||||
type FileFieldsImageMetaSortInput = {
|
||||
readonly datePublished: InputMaybe<SortOrderEnum>;
|
||||
readonly dateTaken: InputMaybe<SortOrderEnum>;
|
||||
readonly dominantHue: InputMaybe<SortOrderEnum>;
|
||||
readonly meta: InputMaybe<FileFieldsImageMetaMetaSortInput>;
|
||||
@@ -1508,6 +1532,7 @@ 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>;
|
||||
@@ -1516,6 +1541,7 @@ type Query = {
|
||||
readonly siteFunction: Maybe<SiteFunction>;
|
||||
readonly sitePage: Maybe<SitePage>;
|
||||
readonly sitePlugin: Maybe<SitePlugin>;
|
||||
readonly staticImage: Maybe<StaticImage>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1583,6 +1609,14 @@ 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>;
|
||||
@@ -1752,6 +1786,46 @@ 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>;
|
||||
@@ -2518,6 +2592,312 @@ 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']>;
|
||||
@@ -2547,10 +2927,12 @@ type GalleryImageQueryVariables = Exact<{
|
||||
|
||||
type GalleryImageQuery = { readonly file: { readonly base: string, readonly publicURL: string | null, readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData, readonly fluid: { readonly aspectRatio: number } | null } | null, readonly fields: { readonly imageMeta: { readonly dateTaken: string | null, readonly meta: { readonly Make: string | null, readonly Model: string | null, readonly ExposureTime: number | null, readonly FNumber: number | null, readonly ISO: number | null, readonly DateTimeOriginal: string | null, readonly CreateDate: string | null, readonly ShutterSpeedValue: number | null, readonly ApertureValue: number | null, readonly FocalLength: number | null, readonly LensModel: string | null, readonly ObjectName: string | null, readonly Caption: string | null, readonly Location: string | null, readonly City: string | null, readonly State: string | null } | null, readonly vibrant: { readonly DarkMuted: ReadonlyArray<number | null> | null, readonly DarkVibrant: ReadonlyArray<number | null> | null, readonly LightMuted: ReadonlyArray<number | null> | null, readonly LightVibrant: ReadonlyArray<number | null> | null, readonly Vibrant: ReadonlyArray<number | null> | null, readonly Muted: ReadonlyArray<number | null> | null } | null } | null } | null } | null };
|
||||
|
||||
type GalleryImageFileFragment = { readonly nodes: ReadonlyArray<{ readonly base: string, readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData, readonly fluid: { readonly aspectRatio: number } | null } | null, readonly fields: { readonly imageMeta: { readonly vibrantHue: number | null, readonly dominantHue: ReadonlyArray<number | null> | null, readonly dateTaken: string | null, readonly datePublished: string | null, readonly meta: { readonly Keywords: ReadonlyArray<string | null> | null, readonly Rating: number | null, readonly ObjectName: string | null, readonly CreateDate: string | null, readonly ModifyDate: string | null } | null, readonly vibrant: { readonly DarkMuted: ReadonlyArray<number | null> | null, readonly DarkVibrant: ReadonlyArray<number | null> | null, readonly LightMuted: ReadonlyArray<number | null> | null, readonly LightVibrant: ReadonlyArray<number | null> | null, readonly Vibrant: ReadonlyArray<number | null> | null, readonly Muted: ReadonlyArray<number | null> | null } | null } | null } | null }> };
|
||||
|
||||
type GalleryPageQueryQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
type GalleryPageQueryQuery = { readonly allFile: { readonly nodes: ReadonlyArray<{ readonly relativePath: string, readonly base: string, readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData, readonly fluid: { readonly aspectRatio: number } | null } | null, readonly fields: { readonly imageMeta: { readonly vibrantHue: number | null, readonly dominantHue: ReadonlyArray<number | null> | null, readonly dateTaken: string | null, readonly meta: { readonly Keywords: ReadonlyArray<string | null> | null, readonly Rating: number | null, readonly ObjectName: string | null } | null, readonly vibrant: { readonly DarkMuted: ReadonlyArray<number | null> | null, readonly DarkVibrant: ReadonlyArray<number | null> | null, readonly LightMuted: ReadonlyArray<number | null> | null, readonly LightVibrant: ReadonlyArray<number | null> | null, readonly Vibrant: ReadonlyArray<number | null> | null, readonly Muted: ReadonlyArray<number | null> | null } | null } | null } | null }> } };
|
||||
type GalleryPageQueryQuery = { readonly recents: { readonly nodes: ReadonlyArray<{ readonly base: string, readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData, readonly fluid: { readonly aspectRatio: number } | null } | null, readonly fields: { readonly imageMeta: { readonly vibrantHue: number | null, readonly dominantHue: ReadonlyArray<number | null> | null, readonly dateTaken: string | null, readonly datePublished: string | null, readonly meta: { readonly Keywords: ReadonlyArray<string | null> | null, readonly Rating: number | null, readonly ObjectName: string | null, readonly CreateDate: string | null, readonly ModifyDate: string | null } | null, readonly vibrant: { readonly DarkMuted: ReadonlyArray<number | null> | null, readonly DarkVibrant: ReadonlyArray<number | null> | null, readonly LightMuted: ReadonlyArray<number | null> | null, readonly LightVibrant: ReadonlyArray<number | null> | null, readonly Vibrant: ReadonlyArray<number | null> | null, readonly Muted: ReadonlyArray<number | null> | null } | null } | null } | null }> }, readonly all: { readonly nodes: ReadonlyArray<{ readonly base: string, readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData, readonly fluid: { readonly aspectRatio: number } | null } | null, readonly fields: { readonly imageMeta: { readonly vibrantHue: number | null, readonly dominantHue: ReadonlyArray<number | null> | null, readonly dateTaken: string | null, readonly datePublished: string | null, readonly meta: { readonly Keywords: ReadonlyArray<string | null> | null, readonly Rating: number | null, readonly ObjectName: string | null, readonly CreateDate: string | null, readonly ModifyDate: string | null } | null, readonly vibrant: { readonly DarkMuted: ReadonlyArray<number | null> | null, readonly DarkVibrant: ReadonlyArray<number | null> | null, readonly LightMuted: ReadonlyArray<number | null> | null, readonly LightVibrant: ReadonlyArray<number | null> | null, readonly Vibrant: ReadonlyArray<number | null> | null, readonly Muted: ReadonlyArray<number | null> | null } | null } | null } | null }> } };
|
||||
|
||||
type GatsbyImageSharpFixedFragment = { readonly base64: string | null, readonly width: number, readonly height: number, readonly src: string, readonly srcSet: string };
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 22 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 466 KiB |
Binary file not shown.
+2
-2
@@ -102,8 +102,8 @@ const IndexPage = ({
|
||||
objectFit={browserIsLandscape ? "cover" : "contain"}
|
||||
style={{
|
||||
height: screenHeight
|
||||
? `${screenHeight - 160}px`
|
||||
: "calc(100vh-160px)",
|
||||
? `${screenHeight - 268}px`
|
||||
: "calc(100vh-268px)",
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
+154
-78
@@ -1,12 +1,13 @@
|
||||
import * as React from "react";
|
||||
import * as R from "ramda";
|
||||
import { graphql, PageProps } from "gatsby";
|
||||
import { graphql, Link, navigate, PageProps } from "gatsby";
|
||||
import { Helmet } from "react-helmet";
|
||||
// import { Picker, Item } from "@adobe/react-spectrum";
|
||||
|
||||
import MasonryGallery from "../components/MasonryGallery";
|
||||
import KeywordsPicker from "../components/KeywordsPicker";
|
||||
import {
|
||||
compareDates,
|
||||
getGalleryPageUrl,
|
||||
getHelmetSafeBodyStyle,
|
||||
getVibrantStyle,
|
||||
@@ -19,27 +20,41 @@ import ColorPalette from "@spectrum-icons/workflow/ColorPalette";
|
||||
const SORT_KEYS = {
|
||||
hue: ["fields", "imageMeta", "vibrantHue"],
|
||||
rating: ["fields", "imageMeta", "meta", "Rating"],
|
||||
hue_debug: ["fields", "imageMeta", "dominantHue", 0],
|
||||
date: [],
|
||||
// hue_debug: ["fields", "imageMeta", "dominantHue", 0],
|
||||
hue_debug: ["fields", "imageMeta", "dominantHue", "0"],
|
||||
date: ["fields", "imageMeta", "dateTaken"],
|
||||
datePublished: ["fields", "imageMeta", "datePublished"],
|
||||
} as const;
|
||||
|
||||
export type GalleryImage =
|
||||
Queries.GalleryPageQueryQuery["allFile"]["nodes"][number];
|
||||
Queries.GalleryPageQueryQuery["all"]["nodes"][number];
|
||||
|
||||
const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
const hash =
|
||||
typeof window !== "undefined" ? window.location.hash.replace("#", "") : "";
|
||||
function smartCompareDates(
|
||||
key: keyof typeof SORT_KEYS,
|
||||
left: GalleryImage,
|
||||
right: GalleryImage
|
||||
) {
|
||||
let diff = compareDates(SORT_KEYS[key], left, right);
|
||||
if (diff !== 0) {
|
||||
return diff;
|
||||
}
|
||||
return compareDates(SORT_KEYS.date, left, right);
|
||||
}
|
||||
|
||||
const [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 GalleryPage = ({
|
||||
data,
|
||||
location,
|
||||
}: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
const hash = location.hash ? location.hash.replace("#", "") : "";
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const filterKeyword = params.get("filter");
|
||||
const sortKey = params.get("sort") ?? "rating";
|
||||
|
||||
const showDebug = Boolean(params.get("debug")?.length);
|
||||
const [showPalette, setShowPalette] = React.useState(false);
|
||||
|
||||
const setKeyword = React.useCallback(
|
||||
const onKeywordPick = React.useCallback(
|
||||
(newKeyword: string | null) => {
|
||||
if (newKeyword) {
|
||||
try {
|
||||
@@ -50,14 +65,8 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
_setKeyword(newKeyword);
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
getGalleryPageUrl({ keyword: newKeyword, sortKey }, hash)
|
||||
);
|
||||
},
|
||||
[_setKeyword, sortKey, hash]
|
||||
[]
|
||||
);
|
||||
|
||||
const setSortKey = React.useCallback(
|
||||
@@ -69,17 +78,21 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
_setSortKey(newSortKey);
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
getGalleryPageUrl({ sortKey: newSortKey, keyword: filterKeyword }, hash)
|
||||
navigate(
|
||||
getGalleryPageUrl(
|
||||
{ sortKey: newSortKey, keyword: filterKeyword, showDebug },
|
||||
hash
|
||||
),
|
||||
{ replace: true }
|
||||
);
|
||||
},
|
||||
[_setSortKey, filterKeyword, hash]
|
||||
[filterKeyword, hash, showDebug]
|
||||
);
|
||||
|
||||
const removeHash = React.useCallback(() => {
|
||||
if (!hash.length) {
|
||||
return;
|
||||
}
|
||||
const url = new URL(
|
||||
typeof window !== "undefined"
|
||||
? window.location.href.toString()
|
||||
@@ -89,55 +102,38 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
url.hash = "";
|
||||
window.history.replaceState(null, "", url.href.toString());
|
||||
window.removeEventListener("wheel", removeHash);
|
||||
setHashCleared(true);
|
||||
}, []);
|
||||
}, [hash]);
|
||||
|
||||
const scrollIntoView = React.useCallback(() => {
|
||||
if (!hash) {
|
||||
React.useEffect(() => {
|
||||
window.addEventListener("wheel", removeHash);
|
||||
return () => window.removeEventListener("wheel", removeHash);
|
||||
}, [removeHash]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// hacky but it works for now
|
||||
requestAnimationFrame(() => {
|
||||
// don't scroll into view if user got here with back button or if we just cleared it
|
||||
if (!hash.length) {
|
||||
return;
|
||||
}
|
||||
const el = document.getElementById(hash);
|
||||
if (!el) {
|
||||
console.log("⚠️failed to find hash");
|
||||
return;
|
||||
}
|
||||
console.log('scrolling into view manually')
|
||||
el.scrollIntoView({
|
||||
block: "center",
|
||||
block: hash.startsWith("all") ? "start" : "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]);
|
||||
});
|
||||
}, [hash]);
|
||||
|
||||
const images: GalleryImage[] = React.useMemo(() => {
|
||||
const sort =
|
||||
sortKey === "date"
|
||||
? R.sort((node1: typeof data["allFile"]["nodes"][number], node2) => {
|
||||
const date1 = new Date(
|
||||
R.pathOr("", ["fields", "imageMeta", "dateTaken"], node1)
|
||||
);
|
||||
const date2 = new Date(
|
||||
R.pathOr("", ["fields", "imageMeta", "dateTaken"], node2)
|
||||
);
|
||||
return -1 * (date1.getTime() - date2.getTime());
|
||||
})
|
||||
sortKey === "date" || sortKey === "datePublished"
|
||||
? R.sort((node1: typeof data["all"]["nodes"][number], node2) =>
|
||||
smartCompareDates(sortKey, node1, node2)
|
||||
)
|
||||
: R.sort(
|
||||
// @ts-ignore
|
||||
R.descend(R.path<GalleryImage>(SORT_KEYS[sortKey]))
|
||||
@@ -157,7 +153,7 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
// @ts-ignore
|
||||
sort,
|
||||
filter
|
||||
)(data.allFile.nodes) as any;
|
||||
)(data.all.nodes) as any;
|
||||
return ret;
|
||||
} catch (e) {
|
||||
console.log("caught images!", e);
|
||||
@@ -165,6 +161,33 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
}
|
||||
}, [data, sortKey, filterKeyword]);
|
||||
|
||||
const recents = React.useMemo(() => {
|
||||
return R.sort(
|
||||
(left, right) => smartCompareDates("datePublished", left, right),
|
||||
data.recents.nodes
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
const dataFn = React.useCallback(
|
||||
(image: GalleryImage): string | null => {
|
||||
if (!showDebug) {
|
||||
return null;
|
||||
}
|
||||
if (sortKey === "rating") {
|
||||
return `[${R.pathOr(null, SORT_KEYS.rating, image)}] ${image.base}`;
|
||||
}
|
||||
if (sortKey === "datePublished") {
|
||||
const date = R.pathOr(null, SORT_KEYS.datePublished, image);
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
return new Date(date).toLocaleString();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[showDebug, sortKey]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* @ts-ignore */}
|
||||
@@ -174,6 +197,7 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
className="bg-white transition-color"
|
||||
// @ts-ignore
|
||||
style={getHelmetSafeBodyStyle(
|
||||
// @ts-ignore shrug
|
||||
getVibrantStyle({
|
||||
Muted: [0, 0, 0],
|
||||
LightMuted: [0, 0, 0],
|
||||
@@ -195,8 +219,47 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="gradient pb-6">
|
||||
<div className="px-4 md:px-8 flex items-baseline">
|
||||
<h3 className="mx-2 font-bold" id="recently">
|
||||
Recently published
|
||||
</h3>
|
||||
{sortKey !== "datePublished" && (
|
||||
<Link
|
||||
className="underline cursor-pointer text-gray-500"
|
||||
to="?sort=datePublished#all"
|
||||
>
|
||||
show more
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<MasonryGallery
|
||||
aspectsByBreakpoint={{
|
||||
xs: 3,
|
||||
sm: 3,
|
||||
md: 4,
|
||||
lg: 4,
|
||||
xl: 5,
|
||||
"2xl": 6,
|
||||
"3xl": 8,
|
||||
}}
|
||||
images={recents}
|
||||
singleRow
|
||||
/>
|
||||
</div>
|
||||
<div className="px-4 md:px-8 mt-2 pt-2">
|
||||
<h3 className="mx-2 font-bold" id="all">
|
||||
All images
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row lg:items-end justify-between px-4 md:px-8 sm:mx-auto">
|
||||
<KeywordsPicker
|
||||
getHref={(val) =>
|
||||
getGalleryPageUrl(
|
||||
{ keyword: val, sortKey, showDebug },
|
||||
hash
|
||||
)
|
||||
}
|
||||
keywords={[
|
||||
"Boyce Thompson Arboretum",
|
||||
"winter",
|
||||
@@ -206,18 +269,18 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
"landscape",
|
||||
"flowers",
|
||||
"product",
|
||||
"waterfall",
|
||||
"fireworks",
|
||||
"panoramic",
|
||||
// "waterfall",
|
||||
// "fireworks",
|
||||
// "panoramic",
|
||||
"Portland Japanese Garden",
|
||||
// "shoot the light",
|
||||
// "sunset",
|
||||
]}
|
||||
onChange={setKeyword}
|
||||
onPick={onKeywordPick}
|
||||
value={filterKeyword}
|
||||
/>
|
||||
<div className="m-2 flex flex-row items-end">
|
||||
<div className="border border-black rounded mr-2">
|
||||
<div className="my-2 mr-2 flex flex-row items-end">
|
||||
<div className="border border-gray-400 rounded mr-2">
|
||||
<Switch
|
||||
isSelected={showPalette}
|
||||
onChange={(val) => setShowPalette(val)}
|
||||
@@ -237,7 +300,8 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
selectedKey={sortKey}
|
||||
>
|
||||
<Item key="rating">Curated</Item>
|
||||
<Item key="date">Date</Item>
|
||||
<Item key="datePublished">Date published</Item>
|
||||
<Item key="date">Date taken</Item>
|
||||
<Item key="hue">Hue</Item>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -253,8 +317,8 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
"2xl": 6.1,
|
||||
"3xl": 8,
|
||||
}}
|
||||
dataFn={dataFn}
|
||||
debugHue={sortKey === "hue_debug"}
|
||||
debugRating={sortKey === "rating" && showDebug}
|
||||
images={images}
|
||||
linkState={{
|
||||
sortKey,
|
||||
@@ -268,12 +332,23 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
|
||||
|
||||
export const query = graphql`
|
||||
query GalleryPageQuery {
|
||||
allFile(
|
||||
recents: allFile(
|
||||
filter: { sourceInstanceName: { eq: "gallery" } }
|
||||
sort: { fields: { imageMeta: { datePublished: DESC } } }
|
||||
limit: 10
|
||||
) {
|
||||
...GalleryImageFile
|
||||
}
|
||||
all: allFile(
|
||||
filter: { sourceInstanceName: { eq: "gallery" } }
|
||||
sort: { fields: { imageMeta: { dateTaken: DESC } } }
|
||||
) {
|
||||
...GalleryImageFile
|
||||
}
|
||||
}
|
||||
|
||||
fragment GalleryImageFile on FileConnection {
|
||||
nodes {
|
||||
relativePath
|
||||
base
|
||||
childImageSharp {
|
||||
fluid {
|
||||
@@ -290,20 +365,21 @@ export const query = graphql`
|
||||
vibrantHue
|
||||
dominantHue
|
||||
dateTaken
|
||||
datePublished
|
||||
meta {
|
||||
Keywords
|
||||
Rating
|
||||
ObjectName
|
||||
CreateDate
|
||||
ModifyDate
|
||||
}
|
||||
vibrant {
|
||||
# Vibrant
|
||||
...VibrantColors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default GalleryPage;
|
||||
|
||||
+3
-15
@@ -8,6 +8,7 @@
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
/* .hero * {
|
||||
transition: color .2s, background-color .2s;
|
||||
@@ -83,21 +84,8 @@
|
||||
.gradient {
|
||||
background-image: linear-gradient(
|
||||
180deg,
|
||||
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%
|
||||
hsla(0deg, 0%, 0%, 0%) 95%,
|
||||
hsla(0deg, 0%, 0%, 10%) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+43
-11
@@ -1,18 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
import { pathOr } from "ramda";
|
||||
// import kebabCase from 'lodash/kebabCase';
|
||||
|
||||
import React from "react";
|
||||
import { HomepageImage } from "./pages";
|
||||
import { GalleryImage } from "./pages/photogallery";
|
||||
|
||||
export const getMeta = <T extends GalleryImage | HomepageImage>(image: T) => image.fields?.imageMeta;
|
||||
export const getMeta = <T extends GalleryImage | HomepageImage>(image: T) =>
|
||||
image.fields?.imageMeta;
|
||||
|
||||
export const getName = (image: GalleryImage) =>
|
||||
image.fields?.imageMeta?.meta?.ObjectName || image.base;
|
||||
image.fields?.imageMeta?.meta?.ObjectName || image.base;
|
||||
|
||||
// some pleasing default colors for SSR and initial hydration
|
||||
export const getVibrant = (image: GalleryImage | HomepageImage) => getMeta(image)?.vibrant;
|
||||
export const getVibrant = (image: GalleryImage | HomepageImage) =>
|
||||
getMeta(image)?.vibrant;
|
||||
|
||||
export const hasName = (image: GalleryImage) => Boolean(image.fields?.imageMeta?.meta?.ObjectName);
|
||||
export const hasName = (image: GalleryImage) =>
|
||||
Boolean(image.fields?.imageMeta?.meta?.ObjectName);
|
||||
|
||||
export const getAspectRatio = (image: GalleryImage | HomepageImage): number =>
|
||||
image.childImageSharp?.fluid?.aspectRatio ?? 1;
|
||||
@@ -25,7 +30,10 @@ export const getCanonicalSize = (image: GalleryImage) => ({
|
||||
export const getRgba = (palette: string[], alpha: number) =>
|
||||
`rgba(${palette[0]}, ${palette[1]}, ${palette[2]}, ${alpha || 1})`;
|
||||
|
||||
export const getVibrantStyle = (vibrant: Queries.FileFieldsImageMetaVibrant, screenHeight?: number) => ({
|
||||
export const getVibrantStyle = (
|
||||
vibrant: Queries.FileFieldsImageMetaVibrant,
|
||||
screenHeight?: number
|
||||
) => ({
|
||||
"--muted": vibrant.Muted,
|
||||
"--dark-muted": vibrant.DarkMuted,
|
||||
"--light-muted": vibrant.LightMuted,
|
||||
@@ -40,10 +48,12 @@ export const getHelmetSafeBodyStyle = (style: React.CSSProperties) => {
|
||||
if (typeof window === "undefined") {
|
||||
return style;
|
||||
}
|
||||
return Object.keys(style)
|
||||
return (
|
||||
Object.keys(style)
|
||||
// @ts-ignore
|
||||
.map((key) => `${key}: ${style[key]};`)
|
||||
.join("");
|
||||
.join("")
|
||||
);
|
||||
};
|
||||
|
||||
const gcd = (a: number, b: number): number => {
|
||||
@@ -55,6 +65,9 @@ const gcd = (a: number, b: number): number => {
|
||||
};
|
||||
|
||||
export const getShutterFractionFromExposureTime = (exposureTime: number) => {
|
||||
if (exposureTime === 0.3333333333333333) {
|
||||
return "1/3";
|
||||
}
|
||||
if (exposureTime === 0.03333333333333333) {
|
||||
return "1/30";
|
||||
}
|
||||
@@ -88,10 +101,14 @@ export const getShutterFractionFromExposureTime = (exposureTime: number) => {
|
||||
|
||||
interface galleryPageUrlProps {
|
||||
keyword: string | null;
|
||||
sortKey: string;
|
||||
sortKey: string | null;
|
||||
showDebug: boolean;
|
||||
}
|
||||
|
||||
export const getGalleryPageUrl = ({ keyword, sortKey }: galleryPageUrlProps, hash: string) => {
|
||||
export const getGalleryPageUrl = (
|
||||
{ keyword, sortKey, showDebug }: galleryPageUrlProps,
|
||||
hash: string
|
||||
) => {
|
||||
const url = new URL(
|
||||
`${
|
||||
typeof window !== "undefined"
|
||||
@@ -107,14 +124,29 @@ export const getGalleryPageUrl = ({ keyword, sortKey }: galleryPageUrlProps, has
|
||||
}
|
||||
}
|
||||
if (sortKey !== undefined) {
|
||||
if (sortKey === "rating") {
|
||||
if (sortKey === "rating" || sortKey === null) {
|
||||
url.searchParams.delete("sort");
|
||||
} else {
|
||||
url.searchParams.set("sort", sortKey);
|
||||
}
|
||||
}
|
||||
if (showDebug) {
|
||||
url.searchParams.set("debug", "true");
|
||||
}
|
||||
if (hash) {
|
||||
url.hash = hash;
|
||||
}
|
||||
return url.href.toString().replace(url.origin, "");
|
||||
};
|
||||
|
||||
export function compareDates<T>(
|
||||
date_path: readonly string[],
|
||||
left: 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 diff = -1 * (date1.getTime() - date2.getTime());
|
||||
return diff;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user