groups
This commit is contained in:
47
site.css
47
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,12 @@ export class BigPicture extends React.PureComponent<Props, {}> {
|
||||
|
||||
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() {
|
||||
|
||||
@@ -20,7 +20,7 @@ interface Row {
|
||||
}
|
||||
|
||||
export class Grid extends React.PureComponent<Props, {}> {
|
||||
static displayName: string = "Grid";
|
||||
static displayName: string = "Grid";
|
||||
|
||||
private gridHeight = 0;
|
||||
|
||||
@@ -31,14 +31,14 @@ export class Grid extends React.PureComponent<Props, {}> {
|
||||
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<Props, {}> {
|
||||
});
|
||||
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 (
|
||||
<Picture
|
||||
image={image}
|
||||
@@ -75,7 +75,7 @@ export class Grid extends React.PureComponent<Props, {}> {
|
||||
<div
|
||||
className="Grid-row"
|
||||
style={{ height: height + "px" }}
|
||||
key={row.images.map(image => image.src).join(",")}
|
||||
key={row.images.map((image) => image.src).join(",")}
|
||||
>
|
||||
{pics}
|
||||
</div>
|
||||
|
||||
@@ -21,8 +21,12 @@ export class ImageSet extends React.PureComponent<Props, {}> {
|
||||
return (
|
||||
<div className="ImageSet" ref={this.divRef}>
|
||||
<h2>
|
||||
<span className="ImageSet-location">{this.props.imageSet.location}</span>
|
||||
<span className="ImageSet-description">{this.props.imageSet.description}</span>
|
||||
<span className="ImageSet-location">
|
||||
{this.props.imageSet.location}
|
||||
</span>
|
||||
<span className="ImageSet-description">
|
||||
{this.props.imageSet.description}
|
||||
</span>
|
||||
</h2>
|
||||
<Grid
|
||||
images={this.props.imageSet.images}
|
||||
|
||||
@@ -24,7 +24,7 @@ export class Picture extends React.PureComponent<Props, State> {
|
||||
static displayName = "Picture";
|
||||
|
||||
state: State = {
|
||||
isMounted: false
|
||||
isMounted: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@@ -52,7 +52,7 @@ export class Picture extends React.PureComponent<Props, State> {
|
||||
onClick={this.props.onClick}
|
||||
src={srcSet.bestSrc}
|
||||
height={this.props.height + "px"}
|
||||
width={this.props.width + "px"}
|
||||
width={Math.floor(this.props.width) + "px"}
|
||||
/>
|
||||
</picture>
|
||||
);
|
||||
@@ -64,7 +64,7 @@ export class Picture extends React.PureComponent<Props, State> {
|
||||
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<Props, State> {
|
||||
return {
|
||||
jpeg: jpegSrcSet.join(","),
|
||||
webp: webpSrcSet.join(","),
|
||||
bestSrc: `img/${bestSize}/${this.props.image.src}`
|
||||
bestSrc: `img/${bestSize}/${this.props.image.src}`,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<Props, State> {
|
||||
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) => (
|
||||
<ImageSet
|
||||
private _renderSet(set: Model.ImageSet) {
|
||||
return (
|
||||
<ImageSet
|
||||
key={set.location + set.description}
|
||||
imageSet={set}
|
||||
pageBottom={this.state.pageBottom}
|
||||
setGridHeight={this._setGridHeight(0)}
|
||||
onImageSelected={this._onImageSelected}
|
||||
width={this.state.width}
|
||||
height={this.state.height}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private _renderSetCovers(sets: Model.ImageSet[]) {
|
||||
return (
|
||||
<div className="Root-setCovers">
|
||||
{sets.map((set) => (
|
||||
<SetCover
|
||||
key={set.location + set.description}
|
||||
imageSet={set}
|
||||
pageBottom={
|
||||
this.state.pageBottom - this._getPreviousGridHeights(idx)
|
||||
}
|
||||
setGridHeight={this._setGridHeight(idx)}
|
||||
onImageSelected={this._onImageSelected}
|
||||
width={this.state.width}
|
||||
height={this.state.height}
|
||||
onClick={() => {
|
||||
this._onSetSelected(set);
|
||||
scrollTo(0, 0);
|
||||
}}
|
||||
width={Math.min(this.state.width, 400)}
|
||||
/>
|
||||
))
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const imageSets = this.state.data
|
||||
? this.state.selectedSet
|
||||
? this._renderSet(this.state.selectedSet)
|
||||
: this._renderSetCovers(this.state.data.sets)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="Root">
|
||||
{this._bigPicture()}
|
||||
<h1>Aaron's Ski Pictures</h1>
|
||||
<h1 onClick={this._onHomeSelected}>Aaron's Ski Pictures</h1>
|
||||
{imageSets}
|
||||
</div>
|
||||
);
|
||||
@@ -102,19 +126,26 @@ export class Root extends React.PureComponent<Props, State> {
|
||||
|
||||
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<Props, State> {
|
||||
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;
|
||||
|
||||
|
||||
48
src/components/set_cover.tsx
Normal file
48
src/components/set_cover.tsx
Normal file
@@ -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<Props, State> {
|
||||
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 (
|
||||
<div className="SetCover" onClick={this.props.onClick}>
|
||||
<Picture
|
||||
image={image}
|
||||
onClick={() => {}}
|
||||
height={height}
|
||||
width={width}
|
||||
/>
|
||||
<h2>
|
||||
<span className="SetCover-location">
|
||||
{this.props.imageSet.location}
|
||||
</span>
|
||||
<span className="SetCover-description">
|
||||
{this.props.imageSet.description}
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user