grid mostly there

This commit is contained in:
2018-12-27 15:17:23 -08:00
parent 907d15d481
commit 9c052aebd2
14 changed files with 1418 additions and 24 deletions

1
dist/index.html vendored Symbolic link
View File

@@ -0,0 +1 @@
../index.html

1
dist/site.css vendored Symbolic link
View File

@@ -0,0 +1 @@
../site.css

View File

@@ -4,7 +4,7 @@
<title>Skiing - Aaron Gutierrez</title> <title>Skiing - Aaron Gutierrez</title>
<link rel="stylesheet" href="site.css"> <link rel="stylesheet" href="site.css">
<meta charshet="utf-8"> <meta charshet="utf-8">
<meta viewport="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
</head> </head>
<body> <body>
<div id="mount"> <div id="mount">
@@ -13,6 +13,6 @@
<script src="./node_modules/react/umd/react.development.js"></script> <script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script> <script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<script type="text/javascript" src="./dist/bundle.js"></script> <script type="text/javascript" src="bundle.js"></script>
</body> </body>
</html> </html>

1191
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,7 @@
"source-map-loader": "^0.2.4", "source-map-loader": "^0.2.4",
"typescript": "^3.2.2", "typescript": "^3.2.2",
"webpack": "^4.28.2", "webpack": "^4.28.2",
"webpack-cli": "^3.1.2" "webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.14"
} }
} }

View File

@@ -0,0 +1,32 @@
html,
body {
background-color: #fff;
color: #335;
font-family: sans-serif;
margin: 0;
padding: 0;
}
hr {
border: 1px solid #eef;
width: 100%;
}
div {
box-sizing: border-box;
}
h1 {
color: #69c;
font-size: 2em;
margin: 1em;
}
.Grid-row {
margin: 0;
padding: 0;
}
.Grid img {
cursor: pointer;
}

69
src/components/grid.tsx Normal file
View File

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

View File

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

52
src/components/root.tsx Normal file
View File

@@ -0,0 +1,52 @@
import { Grid } from "./grid";
import * as Model from "../model";
import * as React from "react";
export interface Props {}
export interface State {
images: Model.Images;
selectedIamge?: string | null;
width: number;
}
export class Root extends React.PureComponent<Props, State> {
static displayName = "Root";
state = {
images: {},
selectedImage: null,
width: window.innerWidth
}
componentDidMount() {
window.fetch(Model.URL)
.then(data => data.json())
.then(json => this.setState({ images: json }))
.catch(e => console.error("Error fetching data", e));
window.onresize = () => {
this.setState({ width: window.innerWidth });
}
}
render() {
return <div className="Root">
<h1>Ski</h1>
<hr />
<Grid
images={ this.state.images }
onImageSelected={ this._onImageSelected }
selectedImage={ this.state.selectedImage }
width={ this.state.width } />
</div>;
}
private _onImageSelected = (key: string) => {
this.setState(state => ({
...state,
selectedImage: key
}));
}
}

View File

@@ -1,11 +1,8 @@
import { Root } from "./root"; import { Root } from "./components/root";
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
window.onload = () => { const body = document.getElementById("mount");
const body = document.getElementById("mount");
const root = <Root name="Friend" />; ReactDOM.render(<Root />, body);
ReactDOM.render(root, body);
};

11
src/model.ts Normal file
View File

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

View File

@@ -1,15 +0,0 @@
import * as React from "react";
export interface Props {
name: String;
}
export interface State {}
export class Root extends React.PureComponent<Props, State> {
static displayName = "Root";
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

View File

@@ -1,7 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"outDir": "./dist/", "outDir": "./dist/",
"baseUrl": ".",
"sourceMap": true, "sourceMap": true,
"noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true,
"target": "es6", "target": "es6",
"jsx": "react", "jsx": "react",

View File

@@ -1,3 +1,5 @@
var path = require("path");
module.exports = { module.exports = {
entry: "./src/index.tsx", entry: "./src/index.tsx",
output: { output: {
@@ -5,9 +7,15 @@ module.exports = {
path: __dirname + "/dist" path: __dirname + "/dist"
}, },
mode: "development",
// Enable sourcemaps for debugging webpack's output. // Enable sourcemaps for debugging webpack's output.
devtool: "source-map", devtool: "source-map",
devServer: {
port: 8080
},
resolve: { resolve: {
// Add '.ts' and '.tsx' as resolvable extensions. // Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".ts", ".tsx", ".js", ".json"] extensions: [".ts", ".tsx", ".js", ".json"]