Skip to content

Commit b2ffe9a

Browse files
committed
Merge pull request #29 from mathieudutour/f/overview
2 parents 892bf27 + afdb485 commit b2ffe9a

File tree

8 files changed

+213
-105
lines changed

8 files changed

+213
-105
lines changed

README.markdown

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Check it out:
3535

3636
![http://i.imgur.com/H7o2qHI.gif](http://i.imgur.com/H7o2qHI.gif_)
3737

38+
You can toggle the presenter or overview mode by pressing respectively `p` and `o`.
39+
3840
## PDF Export
3941

4042
Exporting a totally sweet looking PDF from your totally sweet looking Spectacle presentation is asburdly easy.

src/appear.jsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ const Appear = React.createClass({
99
},
1010
contextTypes: {
1111
flux: React.PropTypes.object,
12-
router: React.PropTypes.object,
12+
export: React.PropTypes.bool,
13+
overview: React.PropTypes.bool,
1314
slide: React.PropTypes.number
1415
},
1516
getInitialState() {
@@ -20,8 +21,7 @@ const Appear = React.createClass({
2021
},
2122
componentDidMount() {
2223
this.context.flux.stores.SlideStore.listen(this._storeChange);
23-
const slide = "slide" in this.context.router.state.params ?
24-
this.context.router.state.params.slide : 0;
24+
const slide = this.context.slide;
2525
this.context.flux.actions.SlideActions.addFragment({
2626
slide,
2727
id: this._reactInternalInstance._rootNodeID,
@@ -32,8 +32,7 @@ const Appear = React.createClass({
3232
this.context.flux.stores.SlideStore.unlisten(this._storeChange);
3333
},
3434
_storeChange(state) {
35-
const slide = "slide" in this.context.router.state.params ?
36-
this.context.router.state.params.slide : 0;
35+
const slide = this.context.slide;
3736
const key = _.findKey(state.fragments[slide], {
3837
"id": this._reactInternalInstance._rootNodeID
3938
});
@@ -42,8 +41,7 @@ const Appear = React.createClass({
4241
active: state.fragments[slide][key].visible
4342
}, () => {
4443
let endVal = this.state.active ? 1 : 0;
45-
if (this.context.router.state.location.query &&
46-
"export" in this.context.router.state.location.query) {
44+
if (this.context.export || this.context.overview) {
4745
endVal = 1;
4846
}
4947
this.tweenState("opacity", {

src/deck.jsx

Lines changed: 70 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import cloneWithProps from "react/lib/cloneWithProps";
77
import Radium from "radium";
88
import _ from "lodash";
99
import Presenter from "./presenter";
10+
import Export from "./export";
11+
import Overview from "./overview";
1012

1113
React.initializeTouchEvents(true);
1214

@@ -27,8 +29,7 @@ class Deck extends React.Component {
2729
};
2830
}
2931
componentDidMount() {
30-
const slide = "slide" in this.context.router.state.params ?
31-
parseInt(this.context.router.state.params.slide) : 0;
32+
const slide = this.context.slide;
3233
this.setState({
3334
lastSlide: slide
3435
});
@@ -57,31 +58,50 @@ class Deck extends React.Component {
5758
if (event.keyCode === 39 || event.keyCode === 34) {
5859
this._nextSlide();
5960
}
61+
if (event.keyCode === 79) { // o
62+
this._toggleOverviewMode();
63+
}
64+
if (event.keyCode === 80) { // o
65+
this._togglePresenterMode();
66+
}
67+
}
68+
_toggleOverviewMode() {
69+
const suffix = this.context.overview ? "" : "?overview";
70+
this.context.router.replaceWith("/" + (this.context.slide) + suffix);
71+
}
72+
_togglePresenterMode() {
73+
const suffix = this.context.presenter ? "" : "?presenter";
74+
this.context.router.replaceWith("/" + (this.context.slide) + suffix);
75+
}
76+
_getSuffix() {
77+
if (this.context.presenter) {
78+
return "?presenter";
79+
} else if (this.context.overview) {
80+
return "?overview";
81+
} else {
82+
return "";
83+
}
6084
}
6185
_goToSlide(e) {
6286
if (e.key === "spectacle-slide") {
6387
const data = JSON.parse(e.newValue);
64-
const presenter = this.context.presenter ? "?presenter" : "";
65-
const slide = "slide" in this.context.router.state.params ?
66-
parseInt(this.context.router.state.params.slide) : 0;
88+
const slide = this.context.slide;
6789
this.setState({
6890
lastSlide: slide || 0
6991
});
7092
if (this._checkFragments(slide, data.forward)) {
71-
this.context.router.replaceWith("/" + (data.slide) + presenter);
93+
this.context.router.replaceWith("/" + (data.slide) + this._getSuffix());
7294
}
7395
}
7496
}
7597
_prevSlide() {
76-
const slide = "slide" in this.context.router.state.params ?
77-
parseInt(this.context.router.state.params.slide) : 0;
78-
const presenter = this.context.presenter ? "?presenter" : "";
98+
const slide = this.context.slide;
7999
this.setState({
80100
lastSlide: slide
81101
});
82-
if (this._checkFragments(slide, false)) {
102+
if (this._checkFragments(slide, false) || this.context.overview) {
83103
if (slide > 0) {
84-
this.context.router.replaceWith("/" + (slide - 1) + presenter);
104+
this.context.router.replaceWith("/" + (slide - 1) + this._getSuffix());
85105
localStorage.setItem("spectacle-slide",
86106
JSON.stringify({slide: slide - 1, forward: false, time: Date.now()}));
87107
}
@@ -91,15 +111,13 @@ class Deck extends React.Component {
91111
}
92112
}
93113
_nextSlide() {
94-
const slide = "slide" in this.context.router.state.params ?
95-
parseInt(this.context.router.state.params.slide) : 0;
96-
const presenter = this.context.presenter ? "?presenter" : "";
114+
const slide = this.context.slide;
97115
this.setState({
98116
lastSlide: slide
99117
});
100-
if (this._checkFragments(slide, true)) {
118+
if (this._checkFragments(slide, true) || this.context.overview) {
101119
if (slide < this.props.children.length - 1) {
102-
this.context.router.replaceWith("/" + (slide + 1) + presenter);
120+
this.context.router.replaceWith("/" + (slide + 1) + this._getSuffix());
103121
localStorage.setItem("spectacle-slide",
104122
JSON.stringify({slide: slide + 1, forward: true, time: Date.now()}));
105123
}
@@ -235,52 +253,22 @@ class Deck extends React.Component {
235253
return 0;
236254
}
237255
_renderSlide() {
238-
const slide = "slide" in this.context.router.state.params ?
239-
parseInt(this.context.router.state.params.slide) : 0;
240-
if (this.context.router.state.location.query &&
241-
"export" in this.context.router.state.location.query) {
242-
return this.props.children.map((child, index) => {
243-
return cloneWithProps(child, {
244-
key: index,
245-
slideIndex: slide,
246-
lastSlide: this.state.lastSlide,
247-
transition: child.props.transition.length ?
248-
child.props.transition :
249-
this.props.transition,
250-
transitionDuration: child.props.transition.transitionDuration ?
251-
child.props.transitionDuration :
252-
this.props.transitionDuration
253-
});
254-
});
255-
} else {
256-
const child = this.props.children[slide];
257-
return cloneWithProps(child, {
258-
key: slide,
259-
slideIndex: slide,
260-
lastSlide: this.state.lastSlide,
261-
transition: child.props.transition.length ?
262-
child.props.transition :
263-
this.props.transition,
264-
transitionDuration: child.props.transition.transitionDuration ?
265-
child.props.transitionDuration :
266-
this.props.transitionDuration
267-
});
268-
}
256+
const slide = this.context.slide;
257+
const child = this.props.children[slide];
258+
return cloneWithProps(child, {
259+
key: slide,
260+
slideIndex: slide,
261+
lastSlide: this.state.lastSlide,
262+
transition: child.props.transition.length ?
263+
child.props.transition :
264+
this.props.transition,
265+
transitionDuration: child.props.transition.transitionDuration ?
266+
child.props.transitionDuration :
267+
this.props.transitionDuration
268+
});
269269
}
270270
render() {
271-
let exportMode = false;
272-
let showProgress = true;
273-
274-
if (this.context.router.state.location.query &&
275-
"export" in this.context.router.state.location.query) {
276-
exportMode = true;
277-
showProgress = false;
278-
}
279-
280-
const slide = "slide" in this.context.router.state.params ?
281-
parseInt(this.context.router.state.params.slide) : 0;
282-
283-
const globals = exportMode ? {
271+
const globals = this.context.export ? {
284272
body: {
285273
minWidth: 1100,
286274
minHeight: 850,
@@ -290,7 +278,7 @@ class Deck extends React.Component {
290278

291279
const styles = {
292280
deck: {
293-
backgroundColor: this.context.presenter ? "black" : "",
281+
backgroundColor: this.context.presenter || this.context.overview ? "black" : "",
294282
position: "absolute",
295283
top: 0,
296284
left: 0,
@@ -305,26 +293,30 @@ class Deck extends React.Component {
305293
}
306294
};
307295

308-
const currentSlide = "slide" in this.context.router.state.params ?
309-
parseInt(this.context.router.state.params.slide) : 0;
310-
const slides = this.props.children;
296+
let componentToRender;
297+
if (this.context.presenter) {
298+
componentToRender = (<Presenter slides={this.props.children}
299+
slide={this.context.slide} lastSlide={this.state.lastSlide} />);
300+
} else if (this.context.export) {
301+
componentToRender = <Export slides={this.props.children} />;
302+
} else if (this.context.overview) {
303+
componentToRender = <Overview slides={this.props.children} slide={this.context.slide} />;
304+
} else {
305+
componentToRender = (<TransitionGroup component="div" style={[styles.transition]}>
306+
{this._renderSlide()}
307+
</TransitionGroup>);
308+
}
311309

312310
return (
313311
<div
314312
className="spectacle-deck"
315313
style={[styles.deck]}
316314
onClick={this._handleClick}
317315
{...this._getTouchEvents()}>
318-
{this.context.presenter ?
319-
<Presenter slides={this.props.children}
320-
slide={slide} lastSlide={this.state.lastSlide}/> :
321-
<TransitionGroup
322-
component="div"
323-
className="spectacle-transition"
324-
style={[styles.transition]}>
325-
{this._renderSlide()}
326-
</TransitionGroup>}
327-
{showProgress ? <Progress items={slides} currentSlide={currentSlide}
316+
{componentToRender}
317+
{!this.context.export ? <Progress
318+
items={this.props.children}
319+
currentSlide={this.context.slide}
328320
type={this.props.progress}/> : ""}
329321
<Style rules={assign(this.context.styles.global, globals)} />
330322
</div>
@@ -350,7 +342,10 @@ Deck.contextTypes = {
350342
styles: React.PropTypes.object,
351343
router: React.PropTypes.object,
352344
flux: React.PropTypes.object,
353-
presenter: React.PropTypes.bool
345+
presenter: React.PropTypes.bool,
346+
export: React.PropTypes.bool,
347+
overview: React.PropTypes.bool,
348+
slide: React.PropTypes.number
354349
};
355350

356351
export default Deck;

src/export.jsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from "react/addons";
2+
import cloneWithProps from "react/lib/cloneWithProps";
3+
import Radium from "radium";
4+
5+
@Radium
6+
class Export extends React.Component {
7+
constructor(props) {
8+
super(props);
9+
}
10+
_renderSlides() {
11+
return this.props.slides.map((child, index) => {
12+
return cloneWithProps(child, {
13+
key: index,
14+
slideIndex: index,
15+
transition: [],
16+
transitionDuration: 0
17+
});
18+
});
19+
}
20+
render() {
21+
const styles = {
22+
export: {
23+
height: "100%",
24+
width: "100%"
25+
}
26+
};
27+
return (
28+
<div className="spectacle-export" style={[styles.export]}>
29+
{this._renderSlides()}
30+
</div>
31+
);
32+
}
33+
}
34+
35+
Export.propTypes = {
36+
slides: React.PropTypes.array
37+
};
38+
39+
Export.contextTypes = {
40+
styles: React.PropTypes.object
41+
};
42+
43+
export default Export;

src/overview.jsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*global setInterval*/
2+
3+
import React from "react/addons";
4+
import cloneWithProps from "react/lib/cloneWithProps";
5+
import Base from "./base";
6+
import Radium from "radium";
7+
8+
@Radium
9+
class Overview extends Base {
10+
constructor(props) {
11+
super(props);
12+
}
13+
_renderSlides() {
14+
const slide = this.props.slide;
15+
return this.props.slides.map((child, index) => {
16+
if (index < slide - 3 || slide + 3 < index) { return false; }
17+
const left = (50 + 16.6667 * (index - slide)) + "%";
18+
const style = {
19+
position: "absolute",
20+
width: "15%",
21+
height: "15%",
22+
top: "50%",
23+
left,
24+
transform: " translate(-50%,-50%)",
25+
border: "1px solid #FFF",
26+
opacity: index === slide ? 1 : 0.5
27+
};
28+
const el = cloneWithProps(child, {
29+
key: index,
30+
slideIndex: index,
31+
transition: [],
32+
transitionDuration: 0,
33+
appearOff: true
34+
});
35+
return (
36+
<div style={[style]}>
37+
{el}
38+
</div>
39+
);
40+
});
41+
}
42+
render() {
43+
const styles = {
44+
overview: {
45+
height: "100%",
46+
width: "100%"
47+
}
48+
};
49+
return (
50+
<div className="spectacle-overview" style={[styles.overview]}>
51+
{this._renderSlides()}
52+
</div>
53+
);
54+
}
55+
}
56+
57+
Overview.propTypes = {
58+
slides: React.PropTypes.array,
59+
slide: React.PropTypes.number
60+
};
61+
62+
Overview.contextTypes = {
63+
styles: React.PropTypes.object
64+
};
65+
66+
export default Overview;

0 commit comments

Comments
 (0)