This commit is contained in:
2018-12-28 15:18:48 -07:00
parent b3820ca43b
commit 53fa2be4fa
6 changed files with 219 additions and 196 deletions

View File

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

View File

@@ -4,71 +4,76 @@ import * as Model from "../model";
import * as React from "react"; import * as React from "react";
export interface Props { export interface Props {
images: Model.Image[]; images: Model.Image[];
onImageSelected: (image: Model.Image) => 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 { interface Row {
images: Model.Image[]; images: Model.Image[];
width: number; width: number;
} }
export class Grid extends React.PureComponent<Props, {}> { export class Grid extends React.PureComponent<Props, {}> {
static displayName = "Grid"; static displayName = "Grid";
render() { render() {
let row: Model.Image[] = []; let row: Model.Image[] = [];
const rows: Row[] = []; const rows: Row[] = [];
let rowWidth = 0; let rowWidth = 0;
this.props.images.forEach(image => { this.props.images.forEach(image => {
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({
images: row,
width: rowWidth
});
row = [];
rowWidth = (image.width/image.height);
} else {
rowWidth = newWidth;
}
row.push(image);
});
rows.push({ rows.push({
images: row, images: row,
width: rowWidth width: rowWidth
}); });
const images = rows.map(row => { row = [];
const height = this.props.width / row.width; rowWidth = image.width / image.height;
} else {
rowWidth = newWidth;
}
row.push(image);
});
rows.push({
images: row,
width: rowWidth
});
const pics = row.images.map(image => { const images = rows.map(row => {
return <Picture const height = this.props.width / row.width;
image={image}
onClick={() => this.props.onImageSelected(image)}
key={image.src}
width={image.width/image.height * height}
/>
});
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>; const pics = row.images.map(image => {
} return (
<Picture
image={image}
onClick={() => this.props.onImageSelected(image)}
key={image.src}
width={(image.width / image.height) * height}
/>
);
});
return (
<div
className="Grid-row"
style={{ height: height + "px" }}
key={row.images.map(image => image.src).join(",")}
>
{pics}
</div>
);
});
private _rowHeight = (): number => return <div className="Grid">{images}</div>;
this.props.width > 500 ? ROW_HEIGHT : MOBILE_ROW_HEIGHT; }
private _rowHeight = (): number =>
this.props.width > 500 ? ROW_HEIGHT : MOBILE_ROW_HEIGHT;
} }

View File

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

View File

@@ -3,39 +3,42 @@ import * as Model from "../model";
import * as React from "react"; import * as React from "react";
export interface Props { export interface Props {
image: Model.Image; image: Model.Image;
onClick: () => void; onClick: () => void;
width: number; width: number;
} }
export class Picture extends React.PureComponent<Props, {}> { export class Picture extends React.PureComponent<Props, {}> {
static displayName = "Picture"; static displayName = "Picture";
render() { render() {
const src = `img/600/${this.props.image.src}`; const src = `img/600/${this.props.image.src}`;
return <img return (
onClick={this.props.onClick} <img
srcSet={this._srcset()} onClick={this.props.onClick}
src={src} srcSet={this._srcset()}
width={ this.props.width + "px" } src={src}
/>; width={this.props.width + "px"}
} />
);
}
private _srcset = (): string => { private _srcset = (): string => {
const srcs: string[] = []; const srcs: string[] = [];
Model.SIZES.forEach(size => { Model.SIZES.forEach(size => {
const width = this.props.image.width > this.props.image.height const width =
? size this.props.image.width > this.props.image.height
: this.props.image.width / this.props.image.height * size; ? size
: (this.props.image.width / this.props.image.height) * size;
const scale = width / this.props.width; const scale = width / this.props.width;
if (scale >= 1) { if (scale >= 1) {
srcs.push(`img/${size}/${this.props.image.src} ${scale}x`); srcs.push(`img/${size}/${this.props.image.src} ${scale}x`);
} }
}); });
return srcs.join(","); return srcs.join(",");
} };
} }

View File

@@ -7,84 +7,88 @@ import * as React from "react";
export interface Props {} export interface Props {}
export interface State { export interface State {
data?: Model.Data | null; data?: Model.Data | null;
selectedImage?: Model.Image | null; selectedImage?: Model.Image | null;
width: number; width: number;
} }
export class Root extends React.PureComponent<Props, State> { export class Root extends React.PureComponent<Props, State> {
static displayName = "Root"; static displayName = "Root";
state: State = { state: State = {
width: window.innerWidth width: window.innerWidth
} };
componentDidMount() { componentDidMount() {
window.fetch(Model.URL) window
.then(data => data.json()) .fetch(Model.URL)
.then(json => this.setState({ data: json })) .then(data => data.json())
.then(this._loadHash) .then(json => this.setState({ data: json }))
.catch(e => console.error("Error fetching data", e)); .then(this._loadHash)
.catch(e => console.error("Error fetching data", e));
window.onresize = () => { window.onresize = () => {
this.setState({ width: window.innerWidth }); this.setState({ width: window.innerWidth });
};
window.onpopstate = this._loadHash;
}
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">
{this._bigPicture()}
<h1>Ski</h1>
{imageSets}
</div>
);
}
private _bigPicture = () =>
this.state.selectedImage ? (
<BigPicture
image={this.state.selectedImage}
onClose={this._showGrid}
width={this.state.width}
/>
) : null;
private _loadHash = () => {
if (window.location.hash.length > 0 && this.state.data) {
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;
} }
});
window.onpopstate = this._loadHash; this.setState({ selectedImage: selectedImage });
} else {
this.setState({ selectedImage: null });
} }
};
render() { private _onImageSelected = (img: Model.Image) => {
const imageSets = this.state.data this.setState({ selectedImage: img });
? this.state.data.sets.map(set => window.history.pushState(null, "", `#${img.src}`);
<ImageSet };
key={set.location + set.description}
imageSet={set}
onImageSelected={this._onImageSelected}
width={this.state.width} />
)
: null;
return <div className="Root"> private _showGrid = () => {
{ this._bigPicture() } this.setState({ selectedImage: null });
<h1>Ski</h1> window.history.pushState(null, "", "#");
{ imageSets } };
</div>;
}
private _bigPicture = () =>
this.state.selectedImage
? <BigPicture
image={this.state.selectedImage}
onClose={this._showGrid}
width={this.state.width} />
: null
private _loadHash = () => {
if (window.location.hash.length > 0 && this.state.data) {
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 {
this.setState({ selectedImage: null });
}
}
private _onImageSelected = (img: Model.Image) => {
this.setState({ selectedImage: img });
window.history.pushState(null, "", `#${img.src}`);
}
private _showGrid = () => {
this.setState({ selectedImage: null });
window.history.pushState(null, "", "#");
}
} }

View File

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