Support multiple image sets

This commit is contained in:
2018-12-28 15:12:02 -07:00
parent 869e448dd3
commit b3820ca43b
6 changed files with 117 additions and 64 deletions

View File

@@ -3,7 +3,6 @@ import * as Model from "../model";
import * as React from "react"; import * as React from "react";
export interface Props { export interface Props {
src: string;
image: Model.Image; image: Model.Image;
onClose: () => void; onClose: () => void;
width: number; width: number;
@@ -13,7 +12,7 @@ export class BigPicture extends React.PureComponent<Props, {}> {
static displayName = "BigPicture"; static displayName = "BigPicture";
render() { render() {
const src = `img/1600/${this.props.src}`; const src = `img/1600/${this.props.image.src}`;
return <div className="BigPicture"> return <div className="BigPicture">
<div <div
className="BigPicture-image" className="BigPicture-image"
@@ -23,16 +22,23 @@ export class BigPicture extends React.PureComponent<Props, {}> {
</div> </div>
<div className="BigPicture-footer"> <div className="BigPicture-footer">
<a className="BigPicture-footerLink" <a className="BigPicture-footerLink"
href={`img/${this.props.src}`} href={`img/${this.props.image.src}`}
target="_blank"> target="_blank">
Download Download
</a> </a>
<span className="BigPicture-footerLink" <span className="BigPicture-footerLink"
onClick={this.props.onClose} onClick={this.props.onClose}
tabIndex={1} > onKeyPress={this._keyPress}
tabIndex={0} >
Close Close
</span> </span>
</div> </div>
</div>; </div>;
} }
private _keyPress = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
this.props.onClose();
}
}
} }

View File

@@ -4,59 +4,66 @@ import * as Model from "../model";
import * as React from "react"; import * as React from "react";
export interface Props { export interface Props {
images: Model.Images; images: Model.Image[];
onImageSelected: (key: string) => void; onImageSelected: (image: Model.Image) => void;
width: number; width: number;
} }
export const ROW_HEIGHT = 200; export const ROW_HEIGHT = 200;
export const MOBILE_ROW_HEIGHT = 100; export const MOBILE_ROW_HEIGHT = 100;
interface Row {
images: Model.Image[];
width: number;
}
export class Grid extends React.PureComponent<Props, {}> { export class Grid extends React.PureComponent<Props, {}> {
static displayName = "Grid"; static displayName = "Grid";
render() { render() {
const keys = Object.keys(this.props.images); let row: Model.Image[] = [];
const rows: Row[] = [];
let row: string[] = [];
const rows: string[][] = [];
const rowWidths: number[] = [];
let rowWidth = 0; let rowWidth = 0;
keys.forEach(key => { this.props.images.forEach(image => {
const image = this.props.images[key];
const newWidth = rowWidth + (image.width/image.height); const newWidth = rowWidth + (image.width/image.height);
const height = this.props.width / newWidth; const height = this.props.width / newWidth;
if (height < this._rowHeight()) { if (height < this._rowHeight()) {
rows.push(row); rows.push({
rowWidths.push(rowWidth); images: row,
width: rowWidth
});
row = []; row = [];
rowWidth = (image.width/image.height); rowWidth = (image.width/image.height);
} else { } else {
rowWidth = newWidth; rowWidth = newWidth;
} }
row.push(key); row.push(image);
});
rows.push({
images: row,
width: rowWidth
}); });
rows.push(row);
rowWidths.push(rowWidth);
const images = rows.map((row, idx) => { const images = rows.map(row => {
const scale = this.props.width / rowWidths[idx]; const height = this.props.width / row.width;
const pics = row.map(key => { const pics = row.images.map(image => {
const image = this.props.images[key];
return <Picture return <Picture
image={image} image={image}
onClick={() => this.props.onImageSelected(key)} onClick={() => this.props.onImageSelected(image)}
src={key} key={image.src}
key={key} width={image.width/image.height * height}
width={image.width/image.height * scale}
/> />
}); });
return <div className="Grid-row" style={{height: scale + "px"}} key={row.join(",")}>{pics}</div>; return <div
className="Grid-row"
style={{height: height + "px"}}
key={row.images.map(image => image.src).join(",")}>
{pics}
</div>;
}); });
return <div className="Grid">{images}</div>; return <div className="Grid">{images}</div>;

View File

@@ -0,0 +1,24 @@
import { Grid } from "./grid";
import * as Model from "../model";
import * as React from "react";
export interface Props {
imageSet: Model.ImageSet;
onImageSelected: (img: Model.Image) => void;
width: number;
}
export class ImageSet extends React.PureComponent<Props, {}> {
static displayName = "ImageSet";
render() {
return <div className="ImageSet">
<h2>{ this.props.imageSet.location } · { this.props.imageSet.description }</h2>
<Grid
images={ this.props.imageSet.images}
onImageSelected={ this.props.onImageSelected }
width={ this.props.width } />
</div>;
}
}

View File

@@ -3,7 +3,6 @@ import * as Model from "../model";
import * as React from "react"; import * as React from "react";
export interface Props { export interface Props {
src: string;
image: Model.Image; image: Model.Image;
onClick: () => void; onClick: () => void;
width: number; width: number;
@@ -13,7 +12,7 @@ export class Picture extends React.PureComponent<Props, {}> {
static displayName = "Picture"; static displayName = "Picture";
render() { render() {
const src = `img/600/${this.props.src}`; const src = `img/600/${this.props.image.src}`;
return <img return <img
onClick={this.props.onClick} onClick={this.props.onClick}
srcSet={this._srcset()} srcSet={this._srcset()}
@@ -33,7 +32,7 @@ export class Picture extends React.PureComponent<Props, {}> {
const scale = width / this.props.width; const scale = width / this.props.width;
if (scale >= 1) { if (scale >= 1) {
srcs.push(`img/${size}/${this.props.src} ${scale}x`); srcs.push(`img/${size}/${this.props.image.src} ${scale}x`);
} }
}); });

View File

@@ -1,5 +1,5 @@
import { BigPicture } from "./big_picture"; import { BigPicture } from "./big_picture";
import { Grid } from "./grid"; import { ImageSet } from "./image_set";
import * as Model from "../model"; import * as Model from "../model";
import * as React from "react"; import * as React from "react";
@@ -7,8 +7,8 @@ import * as React from "react";
export interface Props {} export interface Props {}
export interface State { export interface State {
images: Model.Images; data?: Model.Data | null;
selectedImage?: string | null; selectedImage?: Model.Image | null;
width: number; width: number;
} }
@@ -16,14 +16,14 @@ export class Root extends React.PureComponent<Props, State> {
static displayName = "Root"; static displayName = "Root";
state: State = { state: State = {
images: {},
width: window.innerWidth width: window.innerWidth
} }
componentDidMount() { componentDidMount() {
window.fetch(Model.URL) window.fetch(Model.URL)
.then(data => data.json()) .then(data => data.json())
.then(json => this.setState({ images: json })) .then(json => this.setState({ data: json }))
.then(this._loadHash)
.catch(e => console.error("Error fetching data", e)); .catch(e => console.error("Error fetching data", e));
window.onresize = () => { window.onresize = () => {
@@ -32,45 +32,55 @@ export class Root extends React.PureComponent<Props, State> {
window.onpopstate = this._loadHash; window.onpopstate = this._loadHash;
this._loadHash();
} }
render() { render() {
const imageSets = this.state.data
? this.state.data.sets.map(set =>
<ImageSet
key={set.location + set.description}
imageSet={set}
onImageSelected={this._onImageSelected}
width={this.state.width} />
)
: null;
return <div className="Root"> return <div className="Root">
{ this._bigPicture() } { this._bigPicture() }
<h1>Ski</h1> <h1>Ski</h1>
<h2>CMH Galena</h2> { imageSets }
<Grid </div>;
images={ this.state.images }
onImageSelected={ this._onImageSelected }
width={ this.state.width } />
</div>;
} }
private _bigPicture = () => { private _bigPicture = () =>
if (this.state.selectedImage && this.state.images[this.state.selectedImage]) { this.state.selectedImage
return <BigPicture ? <BigPicture
image={this.state.images[this.state.selectedImage]} image={this.state.selectedImage}
src={this.state.selectedImage} onClose={this._showGrid}
onClose={this._showGrid} width={this.state.width} />
width={this.state.width} : null
/>
} else {
return null;
}
}
private _loadHash = () => { private _loadHash = () => {
if (window.location.hash.length > 0) { if (window.location.hash.length > 0 && this.state.data) {
this.setState({ selectedImage: window.location.hash.slice(1) }); const src = window.location.hash.slice(1);
let selectedImage: Model.Image | null = null;
this.state.data.sets.forEach(set => {
const image = set.images.find(image => image.src === src);
if (image) {
selectedImage = image;
}
});
this.setState({ selectedImage: selectedImage });
} else { } else {
this.setState({ selectedImage: null }); this.setState({ selectedImage: null });
} }
} }
private _onImageSelected = (key: string) => { private _onImageSelected = (img: Model.Image) => {
this.setState({ selectedImage: key }); this.setState({ selectedImage: img });
window.history.pushState(null, "", `#${key}`); window.history.pushState(null, "", `#${img.src}`);
} }
private _showGrid = () => { private _showGrid = () => {

View File

@@ -1,11 +1,18 @@
export const SIZES = [1600, 1200, 800, 600, 400, 200]; export const SIZES = [1600, 1200, 800, 600, 400, 200];
export const URL = "img/data.json"; export const URL = "img/data.json";
export interface Images { export interface Data {
[name: string]: Image sets: ImageSet[]
}
export interface ImageSet {
location: string;
description: string;
images: Image[]
} }
export interface Image { export interface Image {
src: string;
height: number; height: number;
width: number; width: number;
} }