Compare commits

...

6 Commits

16 changed files with 406 additions and 76 deletions

2
.lfsconfig Normal file
View File

@ -0,0 +1,2 @@
[lfs]
url = "https://git.chuckdries.com/chuckdries/Personal-Website.git/info/lfs/"

BIN
data/gallery/DSC00003.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/gallery/DSC00159-2.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/gallery/DSC06719.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/gallery/DSC06803.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/gallery/DSC08263.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/gallery/DSC09447.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/gallery/DSC09454.jpg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -10,6 +10,10 @@ import sharp from "sharp";
import { Palette } from "node-vibrant/lib/color"; import { Palette } from "node-vibrant/lib/color";
import { performance } from "perf_hooks"; import { performance } from "perf_hooks";
import util from 'node:util';
import { exec as _exec } from "child_process";
const exec = util.promisify(_exec)
// const path = require("path"); // const path = require("path");
// const Vibrant = require("node-vibrant"); // const Vibrant = require("node-vibrant");
// const chroma = require("chroma-js"); // const chroma = require("chroma-js");
@ -114,7 +118,8 @@ function transformMetaToNodeData(
metaData: Record<string, unknown>, metaData: Record<string, unknown>,
vibrantData: Palette, vibrantData: Palette,
imagePath: string, imagePath: string,
{ r, g, b }: { r: number; b: number; g: number } { r, g, b }: { r: number; b: number; g: number },
datePublished: string,
) { ) {
const vibrant = vibrantData ? processColors(vibrantData, imagePath) : null; const vibrant = vibrantData ? processColors(vibrantData, imagePath) : null;
const vibrantHue = vibrantData.Vibrant!.getHsl()[0] * 360; const vibrantHue = vibrantData.Vibrant!.getHsl()[0] * 360;
@ -131,6 +136,7 @@ function transformMetaToNodeData(
} }
return { return {
dateTaken: metaData.DateTimeOriginal, dateTaken: metaData.DateTimeOriginal,
datePublished,
meta: { meta: {
Make: metaData.Make, Make: metaData.Make,
Model: metaData.Model, Model: metaData.Model,
@ -139,6 +145,7 @@ function transformMetaToNodeData(
ISO: metaData.ISO, ISO: metaData.ISO,
DateTimeOriginal: metaData.DateTimeOriginal, DateTimeOriginal: metaData.DateTimeOriginal,
CreateDate: metaData.CreateDate, CreateDate: metaData.CreateDate,
ModifyDate: metaData.ModifyDate,
ShutterSpeedValue: metaData.ShutterSpeedValue, ShutterSpeedValue: metaData.ShutterSpeedValue,
ApertureValue: metaData.ApertureValue, ApertureValue: metaData.ApertureValue,
FocalLength: metaData.FocalLength, FocalLength: metaData.FocalLength,
@ -173,6 +180,11 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async function ({
const { createNodeField } = actions; const { createNodeField } = actions;
if (node.internal.type === "File" && node.sourceInstanceName === "gallery") { if (node.internal.type === "File" && node.sourceInstanceName === "gallery") {
const { stdout: datePublished, stderr } = await exec(`git log --diff-filter=A --follow --format=%aI -1 -- ${node.absolutePath}`)
if (stderr.length) {
console.error('something went wrong checking publish date: ', stderr);
}
const metaData = await exifr.parse(node.absolutePath as string, { const metaData = await exifr.parse(node.absolutePath as string, {
iptc: true, iptc: true,
xmp: true, xmp: true,
@ -200,7 +212,8 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async function ({
metaData, metaData,
vibrantData, vibrantData,
node.absolutePath as string, node.absolutePath as string,
dominant dominant,
datePublished
), ),
}); });
} }

193
scratchpad.js Normal file
View File

@ -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
}

View File

@ -91,7 +91,7 @@ function Option({ item, state }: OptionProps) {
return ( return (
<li <li
{...optionProps} {...optionProps}
className={`p-3 outline-none cursor-default flex items-center justify-between ${text} text-sm ${ className={`p-2 outline-none cursor-default flex items-center justify-between ${text} text-sm ${
isFocused ? "bg-transparentblack" : "" isFocused ? "bg-transparentblack" : ""
} ${isSelected ? "font-bold" : ""}`} } ${isSelected ? "font-bold" : ""}`}
ref={ref} ref={ref}

View File

@ -18,7 +18,7 @@ interface Row {
} }
interface MasonryGalleryProps { interface MasonryGalleryProps {
images: GalleryImage[]; images: readonly GalleryImage[];
aspectsByBreakpoint: { aspectsByBreakpoint: {
[breakpoint: string]: number; [breakpoint: string]: number;
}; };
@ -26,15 +26,17 @@ interface MasonryGalleryProps {
debugRating?: boolean; debugRating?: boolean;
linkState?: object; linkState?: object;
showPalette?: boolean; showPalette?: boolean;
singleRow?: boolean;
} }
const MasonryGallery = ({ const MasonryGallery = ({
images, images: _images,
aspectsByBreakpoint: aspectTargetsByBreakpoint, aspectsByBreakpoint: aspectTargetsByBreakpoint,
debugHue, debugHue,
debugRating, debugRating,
linkState, linkState,
showPalette, showPalette,
singleRow,
}: MasonryGalleryProps) => { }: MasonryGalleryProps) => {
const [isClient, setIsClient] = React.useState(false); const [isClient, setIsClient] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
@ -57,8 +59,8 @@ const MasonryGallery = ({
}px)`; }px)`;
const aspectRatios = React.useMemo( const aspectRatios = React.useMemo(
() => R.map(getAspectRatio, images).filter(Boolean), () => R.map(getAspectRatio, _images).filter(Boolean),
[images] [_images]
) as number[]; ) as number[];
const targetAspect = aspectTargetsByBreakpoint[breakpoint]; const targetAspect = aspectTargetsByBreakpoint[breakpoint];
@ -83,6 +85,11 @@ const MasonryGallery = ({
}, },
]; ];
} }
// no-op instead of starting a new row
if (singleRow) {
return [currentRow];
}
// start a new row
return [ return [
...acc, ...acc,
currentRow, currentRow,
@ -97,14 +104,16 @@ const MasonryGallery = ({
), ),
R.indexBy(R.prop("startIndex")) R.indexBy(R.prop("startIndex"))
)(aspectRatios), )(aspectRatios),
[aspectRatios, targetAspect] [aspectRatios, targetAspect, singleRow]
); );
const sortedImageList = React.useMemo( const sortedImageList = React.useMemo(
() => images.map((image) => image.base), () => _images.map((image) => image.base),
[images] [_images]
); );
const images = singleRow ? _images.slice(0, rows[0].images) : _images;
let cursor = 0; let cursor = 0;
return ( return (
<div <div

View File

@ -49,7 +49,7 @@ export function Select<T extends object>(props: AriaSelectProps<T>) {
<button <button
{...mergeProps(buttonProps, focusProps)} {...mergeProps(buttonProps, focusProps)}
className={`py-[5px] px-3 w-[150px] flex flex-row items-center justify-between overflow-hidden cursor-default rounded border hover:bg-transparentblack ${ 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" isFocusVisible ? "border-green-700" : "border-black"
} ${state.isOpen ? "bg-gray-100" : "bg-white"}`} } ${state.isOpen ? "bg-gray-100" : "bg-white"}`}
ref={ref} ref={ref}
> >

20
src/gatsby-types.d.ts vendored
View File

@ -571,6 +571,7 @@ type FileFieldsFilterInput = {
}; };
type FileFieldsImageMeta = { type FileFieldsImageMeta = {
readonly datePublished: Maybe<Scalars['String']>;
readonly dateTaken: Maybe<Scalars['Date']>; readonly dateTaken: Maybe<Scalars['Date']>;
readonly dominantHue: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>; readonly dominantHue: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>;
readonly meta: Maybe<FileFieldsImageMetaMeta>; readonly meta: Maybe<FileFieldsImageMetaMeta>;
@ -587,6 +588,7 @@ type FileFieldsImageMeta_dateTakenArgs = {
}; };
type FileFieldsImageMetaFieldSelector = { type FileFieldsImageMetaFieldSelector = {
readonly datePublished: InputMaybe<FieldSelectorEnum>;
readonly dateTaken: InputMaybe<FieldSelectorEnum>; readonly dateTaken: InputMaybe<FieldSelectorEnum>;
readonly dominantHue: InputMaybe<FieldSelectorEnum>; readonly dominantHue: InputMaybe<FieldSelectorEnum>;
readonly meta: InputMaybe<FileFieldsImageMetaMetaFieldSelector>; readonly meta: InputMaybe<FileFieldsImageMetaMetaFieldSelector>;
@ -595,6 +597,7 @@ type FileFieldsImageMetaFieldSelector = {
}; };
type FileFieldsImageMetaFilterInput = { type FileFieldsImageMetaFilterInput = {
readonly datePublished: InputMaybe<StringQueryOperatorInput>;
readonly dateTaken: InputMaybe<DateQueryOperatorInput>; readonly dateTaken: InputMaybe<DateQueryOperatorInput>;
readonly dominantHue: InputMaybe<FloatQueryOperatorInput>; readonly dominantHue: InputMaybe<FloatQueryOperatorInput>;
readonly meta: InputMaybe<FileFieldsImageMetaMetaFilterInput>; readonly meta: InputMaybe<FileFieldsImageMetaMetaFilterInput>;
@ -617,6 +620,7 @@ type FileFieldsImageMetaMeta = {
readonly Location: Maybe<Scalars['String']>; readonly Location: Maybe<Scalars['String']>;
readonly Make: Maybe<Scalars['String']>; readonly Make: Maybe<Scalars['String']>;
readonly Model: Maybe<Scalars['String']>; readonly Model: Maybe<Scalars['String']>;
readonly ModifyDate: Maybe<Scalars['Date']>;
readonly ObjectName: Maybe<Scalars['String']>; readonly ObjectName: Maybe<Scalars['String']>;
readonly Rating: Maybe<Scalars['Int']>; readonly Rating: Maybe<Scalars['Int']>;
readonly ShutterSpeedValue: Maybe<Scalars['Float']>; readonly ShutterSpeedValue: Maybe<Scalars['Float']>;
@ -639,6 +643,14 @@ type FileFieldsImageMetaMeta_DateTimeOriginalArgs = {
locale: InputMaybe<Scalars['String']>; 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 = { type FileFieldsImageMetaMetaFieldSelector = {
readonly ApertureValue: InputMaybe<FieldSelectorEnum>; readonly ApertureValue: InputMaybe<FieldSelectorEnum>;
readonly Caption: InputMaybe<FieldSelectorEnum>; readonly Caption: InputMaybe<FieldSelectorEnum>;
@ -654,6 +666,7 @@ type FileFieldsImageMetaMetaFieldSelector = {
readonly Location: InputMaybe<FieldSelectorEnum>; readonly Location: InputMaybe<FieldSelectorEnum>;
readonly Make: InputMaybe<FieldSelectorEnum>; readonly Make: InputMaybe<FieldSelectorEnum>;
readonly Model: InputMaybe<FieldSelectorEnum>; readonly Model: InputMaybe<FieldSelectorEnum>;
readonly ModifyDate: InputMaybe<FieldSelectorEnum>;
readonly ObjectName: InputMaybe<FieldSelectorEnum>; readonly ObjectName: InputMaybe<FieldSelectorEnum>;
readonly Rating: InputMaybe<FieldSelectorEnum>; readonly Rating: InputMaybe<FieldSelectorEnum>;
readonly ShutterSpeedValue: InputMaybe<FieldSelectorEnum>; readonly ShutterSpeedValue: InputMaybe<FieldSelectorEnum>;
@ -675,6 +688,7 @@ type FileFieldsImageMetaMetaFilterInput = {
readonly Location: InputMaybe<StringQueryOperatorInput>; readonly Location: InputMaybe<StringQueryOperatorInput>;
readonly Make: InputMaybe<StringQueryOperatorInput>; readonly Make: InputMaybe<StringQueryOperatorInput>;
readonly Model: InputMaybe<StringQueryOperatorInput>; readonly Model: InputMaybe<StringQueryOperatorInput>;
readonly ModifyDate: InputMaybe<DateQueryOperatorInput>;
readonly ObjectName: InputMaybe<StringQueryOperatorInput>; readonly ObjectName: InputMaybe<StringQueryOperatorInput>;
readonly Rating: InputMaybe<IntQueryOperatorInput>; readonly Rating: InputMaybe<IntQueryOperatorInput>;
readonly ShutterSpeedValue: InputMaybe<FloatQueryOperatorInput>; readonly ShutterSpeedValue: InputMaybe<FloatQueryOperatorInput>;
@ -696,6 +710,7 @@ type FileFieldsImageMetaMetaSortInput = {
readonly Location: InputMaybe<SortOrderEnum>; readonly Location: InputMaybe<SortOrderEnum>;
readonly Make: InputMaybe<SortOrderEnum>; readonly Make: InputMaybe<SortOrderEnum>;
readonly Model: InputMaybe<SortOrderEnum>; readonly Model: InputMaybe<SortOrderEnum>;
readonly ModifyDate: InputMaybe<SortOrderEnum>;
readonly ObjectName: InputMaybe<SortOrderEnum>; readonly ObjectName: InputMaybe<SortOrderEnum>;
readonly Rating: InputMaybe<SortOrderEnum>; readonly Rating: InputMaybe<SortOrderEnum>;
readonly ShutterSpeedValue: InputMaybe<SortOrderEnum>; readonly ShutterSpeedValue: InputMaybe<SortOrderEnum>;
@ -703,6 +718,7 @@ type FileFieldsImageMetaMetaSortInput = {
}; };
type FileFieldsImageMetaSortInput = { type FileFieldsImageMetaSortInput = {
readonly datePublished: InputMaybe<SortOrderEnum>;
readonly dateTaken: InputMaybe<SortOrderEnum>; readonly dateTaken: InputMaybe<SortOrderEnum>;
readonly dominantHue: InputMaybe<SortOrderEnum>; readonly dominantHue: InputMaybe<SortOrderEnum>;
readonly meta: InputMaybe<FileFieldsImageMetaMetaSortInput>; readonly meta: InputMaybe<FileFieldsImageMetaMetaSortInput>;
@ -2547,10 +2563,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 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 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 }; type GatsbyImageSharpFixedFragment = { readonly base64: string | null, readonly width: number, readonly height: number, readonly src: string, readonly srcSet: string };

View File

@ -7,6 +7,7 @@ import { Helmet } from "react-helmet";
import MasonryGallery from "../components/MasonryGallery"; import MasonryGallery from "../components/MasonryGallery";
import KeywordsPicker from "../components/KeywordsPicker"; import KeywordsPicker from "../components/KeywordsPicker";
import { import {
compareDates,
getGalleryPageUrl, getGalleryPageUrl,
getHelmetSafeBodyStyle, getHelmetSafeBodyStyle,
getVibrantStyle, getVibrantStyle,
@ -19,12 +20,24 @@ import ColorPalette from "@spectrum-icons/workflow/ColorPalette";
const SORT_KEYS = { const SORT_KEYS = {
hue: ["fields", "imageMeta", "vibrantHue"], hue: ["fields", "imageMeta", "vibrantHue"],
rating: ["fields", "imageMeta", "meta", "Rating"], rating: ["fields", "imageMeta", "meta", "Rating"],
hue_debug: ["fields", "imageMeta", "dominantHue", 0], // hue_debug: ["fields", "imageMeta", "dominantHue", 0],
date: [], hue_debug: ["fields", "imageMeta", "dominantHue", "0"],
date: ["fields", "imageMeta", "dateTaken"],
modified: ["fields", "imageMeta", "datePublished"],
} as const; } as const;
export type GalleryImage = export type GalleryImage =
Queries.GalleryPageQueryQuery["allFile"]["nodes"][number]; Queries.GalleryPageQueryQuery["all"]["nodes"][number];
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 }: PageProps<Queries.GalleryPageQueryQuery>) => { const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
const hash = const hash =
@ -128,16 +141,8 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
const images: GalleryImage[] = React.useMemo(() => { const images: GalleryImage[] = React.useMemo(() => {
const sort = const sort =
sortKey === "date" sortKey === "date" || sortKey === "modified"
? R.sort((node1: typeof data["allFile"]["nodes"][number], node2) => { ? R.sort((node1: typeof data["all"]["nodes"][number], node2) => smartCompareDates(sortKey, node1, node2))
const date1 = new Date(
R.pathOr("", ["fields", "imageMeta", "dateTaken"], node1)
);
const date2 = new Date(
R.pathOr("", ["fields", "imageMeta", "dateTaken"], node2)
);
return -1 * (date1.getTime() - date2.getTime());
})
: R.sort( : R.sort(
// @ts-ignore // @ts-ignore
R.descend(R.path<GalleryImage>(SORT_KEYS[sortKey])) R.descend(R.path<GalleryImage>(SORT_KEYS[sortKey]))
@ -157,7 +162,7 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
// @ts-ignore // @ts-ignore
sort, sort,
filter filter
)(data.allFile.nodes) as any; )(data.all.nodes) as any;
return ret; return ret;
} catch (e) { } catch (e) {
console.log("caught images!", e); console.log("caught images!", e);
@ -165,6 +170,10 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
} }
}, [data, sortKey, filterKeyword]); }, [data, sortKey, filterKeyword]);
const recents = React.useMemo(() => {
return R.sort((left, right) => smartCompareDates('modified', left, right), data.recents.nodes)
}, [data, 'hi'])
return ( return (
<> <>
{/* @ts-ignore */} {/* @ts-ignore */}
@ -174,6 +183,7 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
className="bg-white transition-color" className="bg-white transition-color"
// @ts-ignore // @ts-ignore
style={getHelmetSafeBodyStyle( style={getHelmetSafeBodyStyle(
// @ts-ignore shrug
getVibrantStyle({ getVibrantStyle({
Muted: [0, 0, 0], Muted: [0, 0, 0],
LightMuted: [0, 0, 0], LightMuted: [0, 0, 0],
@ -195,6 +205,29 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
]} ]}
/> />
</div> </div>
<div className="px-4 md:px-8">
<h3 id="recently" className="mx-2 font-bold">
Recently published
</h3>
</div>
<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"> <div className="flex flex-col lg:flex-row lg:items-end justify-between px-4 md:px-8 sm:mx-auto">
<KeywordsPicker <KeywordsPicker
keywords={[ keywords={[
@ -218,17 +251,17 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
/> />
<div className="m-2 flex flex-row items-end"> <div className="m-2 flex flex-row items-end">
<div className="border border-black rounded mr-2"> <div className="border border-black rounded mr-2">
<Switch <Switch
isSelected={showPalette} isSelected={showPalette}
onChange={(val) => setShowPalette(val)} onChange={(val) => setShowPalette(val)}
> >
<ColorPalette <ColorPalette
UNSAFE_style={{ UNSAFE_style={{
width: "24px", width: "24px",
margin: "0 4px", margin: "0 4px",
}} }}
/> />
</Switch> </Switch>
</div> </div>
<Select <Select
label="Sort by..." label="Sort by..."
@ -237,7 +270,8 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
selectedKey={sortKey} selectedKey={sortKey}
> >
<Item key="rating">Curated</Item> <Item key="rating">Curated</Item>
<Item key="date">Date</Item> <Item key="modified">Date published</Item>
<Item key="date">Date taken</Item>
<Item key="hue">Hue</Item> <Item key="hue">Hue</Item>
</Select> </Select>
</div> </div>
@ -268,37 +302,49 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
export const query = graphql` export const query = graphql`
query GalleryPageQuery { query GalleryPageQuery {
allFile( recents: allFile(
filter: { sourceInstanceName: { eq: "gallery" } }
sort: { fields: { imageMeta: { datePublished: DESC } } }
limit: 7
) {
...GalleryImageFile
}
all: allFile(
filter: { sourceInstanceName: { eq: "gallery" } } filter: { sourceInstanceName: { eq: "gallery" } }
sort: { fields: { imageMeta: { dateTaken: DESC } } } sort: { fields: { imageMeta: { dateTaken: DESC } } }
) { ) {
nodes { ...GalleryImageFile
relativePath }
base }
childImageSharp {
fluid { fragment GalleryImageFile on FileConnection {
aspectRatio nodes {
} base
gatsbyImageData( childImageSharp {
layout: CONSTRAINED fluid {
height: 550 aspectRatio
placeholder: DOMINANT_COLOR
)
} }
fields { gatsbyImageData(
imageMeta { layout: CONSTRAINED
vibrantHue height: 550
dominantHue placeholder: DOMINANT_COLOR
dateTaken )
meta { }
Keywords fields {
Rating imageMeta {
ObjectName vibrantHue
} dominantHue
vibrant { dateTaken
# Vibrant datePublished
...VibrantColors meta {
} Keywords
Rating
ObjectName
CreateDate
ModifyDate
}
vibrant {
...VibrantColors
} }
} }
} }

View File

@ -1,18 +1,23 @@
import React from "react";
import { pathOr } from "ramda";
// import kebabCase from 'lodash/kebabCase'; // import kebabCase from 'lodash/kebabCase';
import React from "react";
import { HomepageImage } from "./pages"; import { HomepageImage } from "./pages";
import { GalleryImage } from "./pages/photogallery"; 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) => 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 // 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 => export const getAspectRatio = (image: GalleryImage | HomepageImage): number =>
image.childImageSharp?.fluid?.aspectRatio ?? 1; image.childImageSharp?.fluid?.aspectRatio ?? 1;
@ -25,7 +30,10 @@ export const getCanonicalSize = (image: GalleryImage) => ({
export const getRgba = (palette: string[], alpha: number) => export const getRgba = (palette: string[], alpha: number) =>
`rgba(${palette[0]}, ${palette[1]}, ${palette[2]}, ${alpha || 1})`; `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, "--muted": vibrant.Muted,
"--dark-muted": vibrant.DarkMuted, "--dark-muted": vibrant.DarkMuted,
"--light-muted": vibrant.LightMuted, "--light-muted": vibrant.LightMuted,
@ -40,10 +48,12 @@ export const getHelmetSafeBodyStyle = (style: React.CSSProperties) => {
if (typeof window === "undefined") { if (typeof window === "undefined") {
return style; return style;
} }
return Object.keys(style) return (
// @ts-ignore Object.keys(style)
.map((key) => `${key}: ${style[key]};`) // @ts-ignore
.join(""); .map((key) => `${key}: ${style[key]};`)
.join("")
);
}; };
const gcd = (a: number, b: number): number => { const gcd = (a: number, b: number): number => {
@ -55,6 +65,9 @@ const gcd = (a: number, b: number): number => {
}; };
export const getShutterFractionFromExposureTime = (exposureTime: number) => { export const getShutterFractionFromExposureTime = (exposureTime: number) => {
if (exposureTime === 0.3333333333333333) {
return "1/3";
}
if (exposureTime === 0.03333333333333333) { if (exposureTime === 0.03333333333333333) {
return "1/30"; return "1/30";
} }
@ -91,7 +104,10 @@ interface galleryPageUrlProps {
sortKey: string; sortKey: string;
} }
export const getGalleryPageUrl = ({ keyword, sortKey }: galleryPageUrlProps, hash: string) => { export const getGalleryPageUrl = (
{ keyword, sortKey }: galleryPageUrlProps,
hash: string
) => {
const url = new URL( const url = new URL(
`${ `${
typeof window !== "undefined" typeof window !== "undefined"
@ -118,3 +134,15 @@ export const getGalleryPageUrl = ({ keyword, sortKey }: galleryPageUrlProps, has
} }
return url.href.toString().replace(url.origin, ""); 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;
}