Basic slideshow with forward and backward navigation.
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
body, html, #root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
180
react-slideshow/src/App.js
vendored
180
react-slideshow/src/App.js
vendored
@@ -1,26 +1,166 @@
|
||||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import {
|
||||
HashRouter as Router,
|
||||
Switch,
|
||||
Route,
|
||||
useParams
|
||||
} from "react-router-dom";
|
||||
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
const IMAGE_CHUNK = 256;
|
||||
const roundup = (v, mod) => {
|
||||
let r = v % mod;
|
||||
if (r === 0) {
|
||||
return v;
|
||||
} else {
|
||||
return v + (mod - r);
|
||||
}
|
||||
};
|
||||
|
||||
class Album extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
error: null,
|
||||
mediaItems: null,
|
||||
idx: 0,
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
let {album} = this.props;
|
||||
fetch(process.env.PUBLIC_URL + `/api/album/${album}`)
|
||||
.then(res => res.json())
|
||||
.then(
|
||||
(result) => this.setState({mediaItems: result}),
|
||||
(error) => this.setState({error}),
|
||||
);
|
||||
}
|
||||
render() {
|
||||
// TODO(wathiede): fade transition.
|
||||
// TODO(wathiede): pair-up portrait orientation images.
|
||||
// TODO(wathiede): fetch an image that maintains the originals aspect ratio
|
||||
let w = window.innerWidth * window.devicePixelRatio;
|
||||
let h = window.innerHeight * window.devicePixelRatio;
|
||||
let ratio = w/h;
|
||||
if (ratio > 1) {
|
||||
// Landscape image
|
||||
w = roundup(w, IMAGE_CHUNK);
|
||||
h = (w/ratio).toFixed();
|
||||
} else {
|
||||
// Portrait image
|
||||
h = roundup(h, IMAGE_CHUNK);
|
||||
w = (h/ratio).toFixed();
|
||||
}
|
||||
|
||||
//let w = roundup(window.innerWidth*window.devicePixelRatio, IMAGE_CHUNK);
|
||||
//let h = roundup(window.innerHeight*window.devicePixelRatio, IMAGE_CHUNK);
|
||||
let {idx, error, mediaItems} = this.state;
|
||||
if (error !== null) {
|
||||
return <h2>Error: {JSON.stringify(error)}</h2>;
|
||||
} else if (mediaItems !== null) {
|
||||
let numImages = mediaItems.length;
|
||||
let nextIdx = (idx+1)%numImages;
|
||||
let prevIdx = (numImages+idx-1)%numImages;
|
||||
let image = mediaItems[idx];
|
||||
let nextImage = mediaItems[nextIdx];
|
||||
let style = {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
backgroundColor: 'black',
|
||||
backgroundImage: `url(/api/image/${image.id}?w=${w}&h=${h})`,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center center',
|
||||
backgroundSize: 'cover',
|
||||
};
|
||||
let prefetchStyle = {
|
||||
// display: 'none'
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '25%',
|
||||
height: '25%',
|
||||
};
|
||||
console.log(`window.devicePixelRatio ${window.devicePixelRatio}`);
|
||||
return <div style={style} onClick={(e)=>{
|
||||
e.stopPropagation();
|
||||
this.setState({idx: nextIdx})
|
||||
}}>
|
||||
<img
|
||||
style={prefetchStyle}
|
||||
onClick={(e)=>{
|
||||
e.stopPropagation();
|
||||
this.setState({idx: prevIdx})
|
||||
}}
|
||||
src={`/api/image/${nextImage.id}?w=${w}&h=${h}`} alt="prefetch next" />
|
||||
</div>;
|
||||
} else {
|
||||
return <h2>Loading...</h2>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumIndex extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
error: null,
|
||||
albums: null,
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
fetch(process.env.PUBLIC_URL + "/api/albums")
|
||||
.then(res => res.json())
|
||||
.then(
|
||||
(result) => this.setState({albums: result}),
|
||||
(error) => this.setState({error}),
|
||||
);
|
||||
}
|
||||
render() {
|
||||
let {error, albums} = this.state;
|
||||
if (error !== null) {
|
||||
return <h2>Error: {JSON.stringify(error)}</h2>;
|
||||
} else if (albums !== null) {
|
||||
return albums.map((a) => {
|
||||
let img = <img src="https://via.placeholder.com/256x128" className="mr-3" alt="unset"/>;
|
||||
if (a.coverPhotoMediaItemId !== undefined) {
|
||||
img = <img src={ `/api/image/${a.coverPhotoMediaItemId}?w=256&h=256` } className="mr-3" alt={ a.title }/>
|
||||
}
|
||||
|
||||
let figure = <figure key={ a.id } className="figure">
|
||||
{img}
|
||||
<figcaption className="figure-caption">{ a.title || "No title" } - { a.mediaItemsCount || 0 } photos </figcaption>
|
||||
</figure>;
|
||||
return <a key={ a.id } href={ '#' + a.id }>
|
||||
{ figure }
|
||||
</a>
|
||||
});
|
||||
} else {
|
||||
return <h2>Loading...</h2>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const AlbumRoute = () => {
|
||||
// We can use the `useParams` hook here to access
|
||||
// the dynamic pieces of the URL.
|
||||
let { albumId } = useParams();
|
||||
return <Album album={albumId} />;
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
return <Router>
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<div className="container">
|
||||
<AlbumIndex />
|
||||
</div>
|
||||
</Route>
|
||||
<Route exact path="/:albumId">
|
||||
<AlbumRoute />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
Reference in New Issue
Block a user