Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ The slide tag represents each slide in the presentation. Giving a slide tag an `
|Name|PropType|Description|
|---|---|---|
|align| PropTypes.string | Accepts a space delimited value for positioning interior content. The first value can be `flex-start` (left), `center` (middle), or `flex-end` (right). The second value can be `flex-start` (top) , `center` (middle), or `flex-end` (bottom). You would provide this prop like `align="center center"`, which is its default.
|goTo| PropTypes.number | Used to navigate to a slide for out-of-order presenting. Slide numbers start at `1`. This can also be used to skip slides as well.
|id| PropTypes.string | Used to create a string based hash.
|maxHeight| PropTypes.number | Used to set max dimensions of the Slide.
|maxWidth| PropTypes.number | Used to set max dimensions of the Slide.
Expand Down
3 changes: 2 additions & 1 deletion example/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default class Presentation extends React.Component {
</Slide>
<Slide
id="wait-what"
goTo={4}
transition={[
'fade',
(transitioning, forward) => {
Expand Down Expand Up @@ -93,7 +94,7 @@ export default class Presentation extends React.Component {
overflow = "overflow"
/>
</Slide>
<Slide>
<Slide goTo={3}>
<ComponentPlayground
theme="dark"
/>
Expand Down
32 changes: 30 additions & 2 deletions src/components/__snapshots__/manager.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ exports[`<Manager /> should render correctly. 1`] = `
contentWidth={1000}
controls={true}
dispatch={[Function]}
fragment={
Object {
"fragments": Array [],
}
}
globalStyles={true}
progress="pacman"
route={
Expand Down Expand Up @@ -101,7 +106,11 @@ exports[`<Manager /> should render correctly. 1`] = `
<MockSlide
dispatch={[Function]}
export={false}
fragments={undefined}
fragments={
Object {
"fragments": Array [],
}
}
hash={0}
key=".$0"
lastSlideIndex={0}
Expand Down Expand Up @@ -273,6 +282,11 @@ exports[`<Manager /> should render the export configuration when specified. 1`]
contentWidth={1000}
controls={true}
dispatch={[Function]}
fragment={
Object {
"fragments": Array [],
}
}
globalStyles={true}
progress="pacman"
route={
Expand Down Expand Up @@ -396,6 +410,11 @@ exports[`<Manager /> should render the overview configuration when specified. 1`
contentWidth={1000}
controls={true}
dispatch={[Function]}
fragment={
Object {
"fragments": Array [],
}
}
globalStyles={true}
progress="pacman"
route={
Expand Down Expand Up @@ -567,6 +586,11 @@ exports[`<Manager /> should render with slideset slides 1`] = `
contentWidth={1000}
controls={true}
dispatch={[Function]}
fragment={
Object {
"fragments": Array [],
}
}
globalStyles={true}
progress="pacman"
route={
Expand Down Expand Up @@ -664,7 +688,11 @@ exports[`<Manager /> should render with slideset slides 1`] = `
<MockSlide
dispatch={[Function]}
export={false}
fragments={undefined}
fragments={
Object {
"fragments": Array [],
}
}
hash={1}
key=".$1"
lastSlideIndex={1}
Expand Down
53 changes: 44 additions & 9 deletions src/components/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class Manager extends Component {
slideReference: [],
fullscreen: window.innerHeight === screen.height,
mobile: window.innerWidth < props.contentWidth,
autoplaying: props.autoplay,
autoplaying: props.autoplay
};
}

Expand Down Expand Up @@ -148,6 +148,9 @@ export class Manager extends Component {
componentWillUnmount() {
this._detachEvents();
}

viewedIndexes = new Set();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

viewedIndexes is just a property on the class because we don’t need to re-render the Manager when it changes. Therefore having it in state isn't necessary.


_attachEvents() {
window.addEventListener('storage', this._goToSlide);
window.addEventListener('keydown', this._handleKeyPress);
Expand Down Expand Up @@ -286,6 +289,7 @@ export class Manager extends Component {
this.setState({
lastSlideIndex: slideIndex,
});
this.viewedIndexes.delete(slideIndex);
if (
this._checkFragments(this.props.route.slide, false) ||
this.props.route.params.indexOf('overview') !== -1
Expand Down Expand Up @@ -314,6 +318,27 @@ export class Manager extends Component {
);
}
}
_nextUnviewedIndex() {
const sortedIndexes = Array.from(this.viewedIndexes).sort((a, b) => a - b);
return Math.min(
(sortedIndexes[sortedIndexes.length - 1] || 0) + 1,
this.state.slideReference.length - 1
);
}
_getOffset(slideIndex) {
const { goTo } = this.state.slideReference[slideIndex];
const nextUnviewedIndex = this._nextUnviewedIndex();
if (goTo && !isNaN(parseInt(goTo))) {
const goToIndex = () => {
if (this.viewedIndexes.has(goTo - 1)) {
return this._nextUnviewedIndex();
}
return goTo - 1;
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goToIndex() func prevents the infinite viewing loop. If we’ve already seen that out-of-order slide, let’s go to the next unviewed index.

return goToIndex() - slideIndex;
}
return nextUnviewedIndex - slideIndex;
}
_nextSlide() {
const slideIndex = this._getSlideIndex();
this.setState({
Expand All @@ -331,13 +356,15 @@ export class Manager extends Component {
this._goToSlide({ key: 'spectacle-slide', newValue: slideData });
}
} else if (slideIndex < slideReference.length - 1) {
this.viewedIndexes.add(slideIndex);
const offset = this._getOffset(slideIndex);
this.context.history.replace(
`/${this._getHash(slideIndex + 1) + this._getSuffix()}`
`/${this._getHash(slideIndex + offset) + this._getSuffix()}`
);
localStorage.setItem(
'spectacle-slide',
JSON.stringify({
slide: this._getHash(slideIndex + 1),
slide: this._getHash(slideIndex + offset),
forward: true,
time: Date.now(),
})
Expand Down Expand Up @@ -492,17 +519,25 @@ export class Manager extends Component {
const slideReference = [];
Children.toArray(this.props.children).forEach((child, rootIndex) => {
if (!child.props.hasSlideChildren) {
slideReference.push({
const reference = {
id: child.props.id || slideReference.length,
rootIndex,
});
rootIndex
};
if (child.props.goTo) {
reference.goTo = child.props.goTo;
}
slideReference.push(reference);
} else {
child.props.children.forEach((setSlide, setIndex) => {
slideReference.push({
const reference = {
id: setSlide.props.id || slideReference.length,
setIndex,
rootIndex,
});
};
if (child.props.goTo) {
reference.goTo = child.props.goTo;
}
slideReference.push(reference);
});
}
});
Expand Down Expand Up @@ -671,4 +706,4 @@ export class Manager extends Component {
}
}

export default connect(state => state)(Manager);
export default connect(state => state, null, null, { withRef: true })(Manager);
74 changes: 74 additions & 0 deletions src/components/manager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { mount } from 'enzyme';
import Manager from './manager';
import range from 'lodash/range';

const _mockContext = function(slide, routeParams) {
return {
Expand All @@ -23,6 +24,9 @@ const _mockContext = function(slide, routeParams) {
style: {
globalStyleSet: [],
},
fragment: {
fragments: []
}
}),
dispatch: () => {},
subscribe: () => {},
Expand Down Expand Up @@ -54,6 +58,12 @@ const _mockChildContext = function() {
};

describe('<Manager />', () => {
beforeEach(() => {
window.localStorage = { setItem: () => {} };
});
afterEach(() => {
window.localStorage = undefined;
});
test('should render correctly.', () => {
const wrapper = mount(
<Manager transition={['zoom', 'slide']} transitionDuration={500}>
Expand Down Expand Up @@ -106,4 +116,68 @@ describe('<Manager />', () => {
);
expect(wrapper).toMatchSnapshot();
});

test('should get the next index when using out-of-order viewing', () => {
const wrapper = mount(
<Manager>
{range(0, 10).map(value => <MockSlide key={value} />)}
</Manager>,
{ context: _mockContext(5, []), childContextTypes: _mockChildContext() }
);
const managerInstance = wrapper.instance().getWrappedInstance();
managerInstance.viewedIndexes = new Set([0, 1, 2, 5, 4, 3]);
// The next unviwed index should sort the set and figure out the next
// best slide to go to, since 0 through 5 have been visited, 6 is the best.
expect(managerInstance._nextUnviewedIndex()).toEqual(6);
});

test('should not exceed the maximum number of slides for next index', () => {
const wrapper = mount(
<Manager>
{range(0, 11).map(value => <MockSlide key={value} />)}
</Manager>,
{ context: _mockContext(10, []), childContextTypes: _mockChildContext() }
);
const managerInstance = wrapper.instance().getWrappedInstance();
managerInstance.viewedIndexes = new Set([0, 1, 2, 5, 4, 3, 6, 9, 10, 7, 8]);
// Even though we are on index 10, index 10 is still the next best index
// because there are no more slides in the deck.
expect(managerInstance._nextUnviewedIndex()).toEqual(10);
});

test('should calc a negative offset when routing from a higher index slide to lower', () => {
const wrapper = mount(
<Manager>
<MockSlide />
<MockSlide goTo={4} />
<MockSlide />
<MockSlide goTo={3} />
<MockSlide />
</Manager>,
{ context: _mockContext(3, []), childContextTypes: _mockChildContext() }
);
const managerInstance = wrapper.instance().getWrappedInstance();
managerInstance.viewedIndexes = new Set([0, 1, 3]);
// We are at slide 4 (index 3) which directs us to go to
// slide 3 (index 2) the delta should be 2 - 3, thus -1.
expect(managerInstance._getOffset(3)).toEqual(-1);
});

test('should calc a positive offset when routing from a lower index slide to higher', () => {
const wrapper = mount(
<Manager>
<MockSlide />
<MockSlide goTo={4} />
<MockSlide />
<MockSlide goTo={3} />
<MockSlide />
</Manager>,
{ context: _mockContext(1, []), childContextTypes: _mockChildContext() }
);
const managerInstance = wrapper.instance().getWrappedInstance();
managerInstance.viewedIndexes = new Set([0, 1]);
// We are at slide 2 (index 1) which directs us to go to
// slide 4 (index 3) the delta should be 3 - 1, thus 2.
expect(managerInstance._getOffset(1)).toEqual(2);
});
});