Skip to content

Commit

Permalink
Merge pull request #220 from ubilabs/feat/showcase
Browse files Browse the repository at this point in the history
feat(showcase): add autoplay for stories and slides
  • Loading branch information
KatvonRivia authored Nov 28, 2019
2 parents 4bb5a66 + 68b9cf4 commit 43ea8d9
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 58 deletions.
19 changes: 19 additions & 0 deletions src/scripts/components/autoplay/autoplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {FunctionComponent} from 'react';
import {useInterval} from '../../hooks/use-interval';
import {useHistory} from 'react-router-dom';

interface Props {
autoPlayLink: string | null;
}

const Autoplay: FunctionComponent<Props> = ({autoPlayLink}) => {
const history = useHistory();

useInterval(() => {
history.replace(`${autoPlayLink}`);
}, 5000);

return null;
};

export default Autoplay;
10 changes: 5 additions & 5 deletions src/scripts/components/story-header/story-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ interface Props {
}

const StoryHeader: FunctionComponent<Props> = ({storyIds, story, mode}) => {
const Present = mode === StoryMode.Present;
const Showcase = mode === StoryMode.Showcase;
const isPresenterMode = mode === StoryMode.Present;
const isShowcaseMode = mode === StoryMode.Showcase;

const storyClasses = cx(
styles.storyHeader,
Present && styles.present,
Showcase && styles.showcase
isPresenterMode && styles.present,
isShowcaseMode && styles.showcase
);

const backLink = storyIds ? `/showcase/${storyIds}` : `/${mode}`;
Expand All @@ -33,7 +33,7 @@ const StoryHeader: FunctionComponent<Props> = ({storyIds, story, mode}) => {
</Link>
<div>
<h2 className={styles.title}>{story && story.title}</h2>
{Showcase && (
{isShowcaseMode && (
<h3 className={styles.subtitle}>{story && story.description}</h3>
)}
</div>
Expand Down
30 changes: 13 additions & 17 deletions src/scripts/components/story-pagination/story-pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,47 @@ import {PreviousIcon} from '../icons/previous-icon';
import {NextIcon} from '../icons/next-icon';
import {PlayIcon} from '../icons/play-icon';
import {RemoveIcon} from '../icons/remove-icon';
import {useStoryNavigation} from '../../libs/get-navigation-links';

import {Slide} from '../../types/story';
import {StoryMode} from '../../types/story-mode';

import styles from './story-pagination.styl';

interface Props {
currentPage: number;
storyId: string;
mode: StoryMode;
slides: Slide[];
slideIndex: number;
numberOfSlides: number;
previousSlideLink?: string | null;
nextSlideLink?: string | null;
}

const StoryPagination: FunctionComponent<Props> = ({
currentPage,
mode,
slides
slideIndex,
numberOfSlides,
previousSlideLink,
nextSlideLink
}) => {
const intl = useIntl();
const isPresenterMode = mode === StoryMode.Present;
const classes = cx(styles.pagination, isPresenterMode && styles.present);

const {previousLink, showPrevious, nextLink, showNext} = useStoryNavigation(
slides,
currentPage
);

return (
<div className={classes}>
<div className={styles.controls}>
{showPrevious ? (
<Link to={previousLink} className={styles.icon}>
{previousSlideLink ? (
<Link to={previousSlideLink} className={styles.icon}>
<PreviousIcon />
</Link>
) : (
<div className={styles.emptyIcon} />
)}

<span>
{currentPage + 1}/{slides.length}
{slideIndex + 1}/{numberOfSlides}
</span>

{showNext ? (
<Link to={nextLink} className={styles.icon}>
{nextSlideLink ? (
<Link to={nextSlideLink} className={styles.icon}>
<NextIcon />
</Link>
) : (
Expand Down
40 changes: 28 additions & 12 deletions src/scripts/components/story/story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import Slide from '../slide/slide';
import {State} from '../../reducers';
import config from '../../config/main';
import StoryHeader from '../story-header/story-header';
import {getNavigationData} from '../../libs/get-navigation-data';
import Autoplay from '../autoplay/autoplay';

import {StoryMode} from '../../types/story-mode';

Expand Down Expand Up @@ -40,15 +42,26 @@ const getStoryId = (params: Params, mode: StoryMode) => {
const Story: FunctionComponent<Props> = ({mode}) => {
const params = useParams<Params>();
const storyId = getStoryId(params, mode);
const storyIds = params.storyIds;
const storyIndex = parseInt(params.storyNumber || '0', 10);
const isShowcaseMode = mode === StoryMode.Showcase;
const story = useSelector((state: State) =>
selectedStorySelector(state, storyId)
);
const stories = useSelector(storyListSelector);
const dispatch = useDispatch();
const pageNumber = parseInt(params.page || '0', 10);
const slide = story?.slides[pageNumber];
const slideIndex = parseInt(params.page || '0', 10);
const slide = story?.slides[slideIndex];
const storyListItem = stories.find(storyItem => storyItem.id === storyId);
const defaultView = config.globe.view;
const {autoPlayLink, nextSlideLink, previousSlideLink} = getNavigationData({
mode,
storyId,
storyIndex,
slideIndex,
storyIds,
numberOfSlides: story?.slides.length
});

// fetch story of active storyId
useEffect(() => {
Expand All @@ -73,36 +86,39 @@ const Story: FunctionComponent<Props> = ({mode}) => {

// redirect to first slide when current slide does not exist
if (story && !slide) {
return <Redirect to={`/${mode}/${storyId}/0`} />;
return isShowcaseMode ? (
<Redirect to="/showcase" />
) : (
<Redirect to={`/${mode}/${storyId}/0`} />
);
}

return (
<div className={styles.story}>
{storyListItem && (
<StoryHeader
story={storyListItem}
mode={mode}
storyIds={params.storyIds}
/>
<StoryHeader story={storyListItem} mode={mode} storyIds={storyIds} />
)}

{/* Instead of rendering only the currect slide we map over all slides to
enforce a newly mounted component when the pageNumber changes */}
{story?.slides.map(
(currentSlide, index) =>
index === pageNumber && (
index === slideIndex && (
<Slide mode={mode} slide={currentSlide} key={index} />
)
)}

{story && (
<StoryPagination
currentPage={pageNumber}
storyId={story.id}
mode={mode}
slides={story.slides}
slideIndex={slideIndex}
numberOfSlides={story.slides.length}
previousSlideLink={previousSlideLink}
nextSlideLink={nextSlideLink}
/>
)}

{isShowcaseMode && <Autoplay autoPlayLink={autoPlayLink} />}
</div>
);
};
Expand Down
24 changes: 24 additions & 0 deletions src/scripts/hooks/use-interval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {useEffect, useRef} from 'react';

export const useInterval = (callback: () => void, delay: number) => {
const savedCallback = useRef<() => void | undefined>();

// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);

// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current && savedCallback.current();
}

if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}

return () => {};
}, [delay]);
};
54 changes: 54 additions & 0 deletions src/scripts/libs/get-navigation-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {StoryMode} from '../types/story-mode';

interface Params {
mode: StoryMode;
slideIndex: number;
storyIndex: number;
storyId?: string | null;
storyIds?: string;
numberOfSlides?: number;
}

export const getNavigationData = ({
mode,
slideIndex,
storyIndex,
storyId,
storyIds,
numberOfSlides
}: Params) => {
let autoPlayLink = null;
let nextSlideLink = null;
let previousSlideLink = null;

if (!numberOfSlides) {
return {autoPlayLink, nextSlideLink, previousSlideLink};
}

const previousSlideIndex = slideIndex - 1;
if (previousSlideIndex >= 0) {
previousSlideLink = `/${mode}/${storyId}/${previousSlideIndex}`;
}
const nextSlideIndex = slideIndex + 1;
if (nextSlideIndex < numberOfSlides) {
nextSlideLink = `/${mode}/${storyId}/${nextSlideIndex}`;
}

if (storyIds) {
const stories = storyIds.split('&');
const nextStoryIndex = storyIndex + 1;

// go through all slides of one story
if (slideIndex + 1 < numberOfSlides) {
autoPlayLink = `/showcase/${storyIds}/${storyIndex}/${nextSlideIndex}`;
// when no slides are left, go to first slide of next story
} else if (nextStoryIndex < stories.length) {
autoPlayLink = `/showcase/${storyIds}/${nextStoryIndex}/0`;
// after the last story, return to the beginning
} else {
autoPlayLink = `/showcase/${storyIds}/0/0`;
}
}

return {autoPlayLink, nextSlideLink, previousSlideLink};
};
24 changes: 0 additions & 24 deletions src/scripts/libs/get-navigation-links.ts

This file was deleted.

2 changes: 2 additions & 0 deletions storage/stories/story2/story2-de.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
"slides": [
{
"text": "# Story title\n\n## Das ist ein subheader\n\nLorem ipsum dolor sit amet consectetur adipisicing elit. Dolor option isi nobis quas ut.",
"shortText": "- Lorem ipsum\n\n- Exercitationem\n\n- sapiente\n\n- Exercitationem\n\n- sapiente",
"images": [
"https://storage.googleapis.com/esa-cfs-storage/stories/story2/assets/story2.jpeg"
]
},
{
"text": "# Story title\n\n## Das ist ein subheader\n\nExercitationem, sapiente. Praesentium quidem mollitia explicabo voluptatem aperiam deleniti ut sunt atque eaque, voluptate commodi in.",
"shortText": "- Lorem ipsum\n\n- Exercitationem\n\n- sapiente",
"images": [
"https://storage.googleapis.com/esa-cfs-storage/stories/story2/assets/story2.jpeg"
]
Expand Down
2 changes: 2 additions & 0 deletions storage/stories/story2/story2-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
"slides": [
{
"text": "# Story title\n\n## This is a subheader\n\nLorem ipsum dolor sit amet consectetur adipisicing elit. Dolor option isi nobis quas ut.",
"shortText": "- Lorem ipsum\n\n- Exercitationem\n\n- sapiente\n\n- Exercitationem\n\n- sapiente",
"images": [
"https://storage.googleapis.com/esa-cfs-storage/stories/story2/assets/story2.jpeg"
]
},
{
"text": "# Story title\n\n## This is a subheader\n\nExercitationem, sapiente. Praesentium quidem mollitia explicabo voluptatem aperiam deleniti ut sunt atque eaque, voluptate commodi in.",
"shortText": "- Lorem ipsum\n\n- Exercitationem\n\n- sapiente",
"images": [
"https://storage.googleapis.com/esa-cfs-storage/stories/story2/assets/story2.jpeg"
]
Expand Down
2 changes: 2 additions & 0 deletions storage/stories/story3/story3-de.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
"slides": [
{
"text": "# Story title\n\n## This is a subheader\n\nLorem ipsum dolor sit amet consectetur adipisicing elit. Dolor option isi nobis quas ut.",
"shortText": "- Lorem ipsum\n\n- Exercitationem\n\n- sapiente",
"images": [
"https://storage.googleapis.com/esa-cfs-storage/stories/story3/assets/story3.jpeg"
]
},
{
"text": "# Story title\n\n## This is a subheader\n\nExercitationem, sapiente. Praesentium quidem mollitia explicabo voluptatem aperiam deleniti ut sunt atque eaque, voluptate commodi in.",
"shortText": "- Lorem ipsum\n\n- Exercitationem\n\n- sapiente\n\n- Exercitationem\n\n- sapiente",
"images": [
"https://storage.googleapis.com/esa-cfs-storage/stories/story3/assets/story3.jpeg"
]
Expand Down
2 changes: 2 additions & 0 deletions storage/stories/story3/story3-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
"slides": [
{
"text": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolor option isi nobis quas ut.",
"shortText": "- Lorem ipsum\n\n- Exercitationem\n\n- sapiente",
"images": [
"https://storage.googleapis.com/esa-cfs-storage/stories/story3/assets/story3.jpeg"
]
},
{
"text": "Exercitationem, sapiente. Praesentium quidem mollitia explicabo voluptatem aperiam deleniti ut sunt atque eaque, voluptate commodi in.",
"shortText": "- Lorem ipsum\n\n- Exercitationem\n\n- sapiente\n\n- Exercitationem\n\n- sapiente",
"images": [
"https://storage.googleapis.com/esa-cfs-storage/stories/story3/assets/story3.jpeg"
]
Expand Down

0 comments on commit 43ea8d9

Please sign in to comment.