From c1f33f9df59206d4cce1865775384c9f3cea6087 Mon Sep 17 00:00:00 2001 From: Aaron Gutierrez Date: Sat, 24 Dec 2022 15:22:23 -0700 Subject: [PATCH] groups --- site.css | 47 +++++++++++++-- src/components/big_picture.tsx | 2 + src/components/grid.tsx | 14 ++--- src/components/image_set.tsx | 8 ++- src/components/picture.tsx | 8 +-- src/components/root.tsx | 106 ++++++++++++++++++++++++--------- src/components/set_cover.tsx | 48 +++++++++++++++ 7 files changed, 189 insertions(+), 44 deletions(-) create mode 100644 src/components/set_cover.tsx diff --git a/site.css b/site.css index e6167b4..fe707b2 100644 --- a/site.css +++ b/site.css @@ -4,16 +4,24 @@ body { color: #335; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,Arial,sans-serif; margin: 0; - overflow-x: hidden; padding: 0; } +body { + overflow-x: hidden; +} + +body.no-scroll { + overflow: hidden; +} + div { box-sizing: border-box; } h1 { color: #69c; + cursor: pointer; font-size: 45px; font-weight: lighter; line-height: 60px; @@ -32,15 +40,35 @@ h2 { margin-left: 30px; margin-right: 30px; margin-top: 0px; +} + +.ImageSet h2 { border-bottom: 1px solid #eef; } +.Root-setCovers { + display: grid; + gap: 30px; +} + +.SetCover { + align-items: center; + cursor: pointer; + display: flex; + flex-direction: column; + justify-content: end; + overflow: hidden; +} + .ImageSet-location, -.ImageSet-description { +.ImageSet-description, +.SetCover-location, +.SetCover-description { white-space: nowrap; } -.ImageSet-location:after { +.ImageSet-location:after, +.SetCover-location:after { content: " · "; white-space: break-spaces; } @@ -100,9 +128,20 @@ h2 { color: #ccf; } - h2 { + .ImageSet h2 { border-color: #335; color: #bbb; } } +@media (min-width: 900px) { + .Root-setCovers { + grid-template-columns: 1fr 1fr; + } +} + +@media (min-width: 1400px) { + .Root-setCovers { + grid-template-columns: 1fr 1fr 1fr; + } +} diff --git a/src/components/big_picture.tsx b/src/components/big_picture.tsx index e47cae5..2bd47ae 100644 --- a/src/components/big_picture.tsx +++ b/src/components/big_picture.tsx @@ -14,10 +14,12 @@ export class BigPicture extends React.PureComponent { componentDidMount() { window.addEventListener("keyup", this._onEscape as any); + document.body.classList.add("no-scroll"); } componentWillUnmount() { window.removeEventListener("keyup", this._onEscape as any); + document.body.classList.remove("no-scroll"); } render() { diff --git a/src/components/grid.tsx b/src/components/grid.tsx index aa7e860..6fcff8d 100644 --- a/src/components/grid.tsx +++ b/src/components/grid.tsx @@ -20,7 +20,7 @@ interface Row { } export class Grid extends React.PureComponent { - static displayName: string = "Grid"; + static displayName: string = "Grid"; private gridHeight = 0; @@ -31,14 +31,14 @@ export class Grid extends React.PureComponent { const rows: Row[] = []; let rowWidth = 0; - this.props.images.forEach(image => { + this.props.images.forEach((image) => { const newWidth = rowWidth + image.width / image.height; const height = this.props.width / newWidth; if (height < this._rowHeight()) { rows.push({ images: row, - width: rowWidth + width: rowWidth, }); row = []; @@ -50,13 +50,13 @@ export class Grid extends React.PureComponent { }); rows.push({ images: row, - width: rowWidth + width: rowWidth, }); - const images = rows.map(row => { + const images = rows.map((row) => { const height = Math.min(this.props.height, this.props.width / row.width); - const pics = row.images.map(image => { + const pics = row.images.map((image) => { return ( {
image.src).join(",")} + key={row.images.map((image) => image.src).join(",")} > {pics}
diff --git a/src/components/image_set.tsx b/src/components/image_set.tsx index 65123c7..e522eb5 100644 --- a/src/components/image_set.tsx +++ b/src/components/image_set.tsx @@ -21,8 +21,12 @@ export class ImageSet extends React.PureComponent { return (

- {this.props.imageSet.location} - {this.props.imageSet.description} + + {this.props.imageSet.location} + + + {this.props.imageSet.description} +

{ static displayName = "Picture"; state: State = { - isMounted: false + isMounted: false, }; componentDidMount() { @@ -52,7 +52,7 @@ export class Picture extends React.PureComponent { onClick={this.props.onClick} src={srcSet.bestSrc} height={this.props.height + "px"} - width={this.props.width + "px"} + width={Math.floor(this.props.width) + "px"} /> ); @@ -64,7 +64,7 @@ export class Picture extends React.PureComponent { let bestSize = 800; let bestScale = Infinity; - Model.SIZES.forEach(size => { + Model.SIZES.forEach((size) => { const width = this.props.image.width > this.props.image.height ? size @@ -87,7 +87,7 @@ export class Picture extends React.PureComponent { return { jpeg: jpegSrcSet.join(","), webp: webpSrcSet.join(","), - bestSrc: `img/${bestSize}/${this.props.image.src}` + bestSrc: `img/${bestSize}/${this.props.image.src}`, }; }; } diff --git a/src/components/root.tsx b/src/components/root.tsx index 88811f5..59e2245 100644 --- a/src/components/root.tsx +++ b/src/components/root.tsx @@ -1,5 +1,6 @@ import { BigPicture } from "./big_picture"; import { ImageSet } from "./image_set"; +import { SetCover } from "./set_cover"; import * as Model from "../model"; import * as React from "react"; @@ -9,6 +10,7 @@ export interface Props {} export interface State { data?: Model.Data | null; selectedImage?: Model.Image | null; + selectedSet?: Model.ImageSet | null; gridHeights: number[]; pageBottom: number; width: number; @@ -39,53 +41,75 @@ export class Root extends React.PureComponent { gridHeights: [], pageBottom: this._viewHeight() + window.pageYOffset, width: this._viewWidth(), - height: this._viewHeight() + height: this._viewHeight(), }; componentDidMount() { window .fetch(Model.dataUrl) - .then(data => data.json()) - .then(json => this.setState({ data: json })) + .then((data) => data.json()) + .then((json) => this.setState({ data: json })) .then(this._loadHash) .then(this._onViewChange) - .catch(e => console.error("Error fetching data", e)); + .catch((e) => console.error("Error fetching data", e)); window.onresize = this._onViewChange; window.onscroll = this._onViewChange; try { - screen.orientation.onchange = this._onViewChange; + screen.orientation.onchange = this._onViewChange; } catch (e) {} try { - window.onorientationchange = this._onViewChange; + window.onorientationchange = this._onViewChange; } catch (e) {} window.onpopstate = this._loadHash; } - render() { - const imageSets = this.state.data - ? this.state.data.sets.map((set, idx) => ( - + ); + } + + private _renderSetCovers(sets: Model.ImageSet[]) { + return ( +
+ {sets.map((set) => ( + { + this._onSetSelected(set); + scrollTo(0, 0); + }} + width={Math.min(this.state.width, 400)} /> - )) + ))} +
+ ); + } + + render() { + const imageSets = this.state.data + ? this.state.selectedSet + ? this._renderSet(this.state.selectedSet) + : this._renderSetCovers(this.state.data.sets) : null; return (
{this._bigPicture()} -

Aaron's Ski Pictures

+

Aaron's Ski Pictures

{imageSets}
); @@ -102,19 +126,26 @@ export class Root extends React.PureComponent { private _loadHash = () => { if (window.location.hash.length > 0 && this.state.data) { - const src = window.location.hash.slice(1); - let selectedImage: Model.Image | null = null; + const hash = window.location.hash.slice(1); - this.state.data.sets.forEach(set => { - const image = set.images.find(image => image.src === src); + let selectedImage: Model.Image | null = null; + let selectedSet: Model.ImageSet | null = null; + + this.state.data.sets.forEach((set) => { + if (this._setToHash(set) === hash) { + selectedSet = set; + } + + const image = set.images.find((image) => image.src === hash); if (image) { selectedImage = image; + selectedSet = set; } }); - this.setState({ selectedImage: selectedImage }); + this.setState({ selectedImage, selectedSet }); } else { - this.setState({ selectedImage: null }); + this.setState({ selectedImage: null, selectedSet: null }); } }; @@ -131,16 +162,37 @@ export class Root extends React.PureComponent { window.history.pushState(null, "", `#${img.src}`); }; + private _onSetSelected = (set: Model.ImageSet) => { + this.setState({ selectedSet: set }); + document.title = + set.location + " – " + set.description + " – Skiing - Aaron Gutierrez"; + window.history.pushState(null, "", `#${this._setToHash(set)}`); + }; + + private _onHomeSelected = () => { + this.setState({ + selectedSet: null, + selectedImage: null, + }); + window.history.pushState(null, "", "#"); + document.title = "Skiing - Aaron Gutierrez"; + }; + + private _setToHash = (set: Model.ImageSet) => + set.location.replace(/[^a-zA-Z0-9-_]/g, "-") + + "-" + + set.description.replace(/[^a-zA-Z0-9-_]/g, "-"); + private _showGrid = () => { this.setState({ selectedImage: null }); - window.history.pushState(null, "", "#"); + this._onSetSelected(this.state.selectedSet as Model.ImageSet); }; private _setGridHeight = (grid: number) => (height: number) => { if (this.state.gridHeights[grid] === height) { return; } - this.setState(state => { + this.setState((state) => { const newGridHeights = [...state.gridHeights]; newGridHeights[grid] = height; diff --git a/src/components/set_cover.tsx b/src/components/set_cover.tsx new file mode 100644 index 0000000..2d444a1 --- /dev/null +++ b/src/components/set_cover.tsx @@ -0,0 +1,48 @@ +import { Picture } from "./picture"; +import * as Model from "../model"; + +import * as React from "react"; + +export interface Props { + imageSet: Model.ImageSet; + onClick: () => void; + width: number; +} + +export interface State {} + +export class SetCover extends React.PureComponent { + static displayName = "SetCover"; + + render() { + const image = this.props.imageSet.images[0]; + const isTall = image.height > image.width; + + const height = isTall + ? this.props.width + : (image.height / image.width) * this.props.width; + + const width = isTall + ? (image.width / image.height) * this.props.width + : this.props.width; + + return ( +
+ {}} + height={height} + width={width} + /> +

+ + {this.props.imageSet.location} + + + {this.props.imageSet.description} + +

+
+ ); + } +}