Skip to content

Commit

Permalink
feat(electron): add offline stories (#299)
Browse files Browse the repository at this point in the history
  • Loading branch information
pwambach authored Mar 18, 2020
1 parent 07bd29b commit a2f5a02
Show file tree
Hide file tree
Showing 15 changed files with 150 additions and 53 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"electron:clean": "rm -rf ./dist-electron",
"electron:build": "npm run electron:clean && npm run electron:install && npm run build && electron-builder -mwl --x64 --config electron-builder.json",
"story-packages": "./scripts/create-story-packages.sh",
"upload-storage": "npm run story-packages && gsutil rsync -r -x \".DS_Store\" ./storage gs://esa-cfs-storage/$npm_package_version && gsutil -m setmeta -r -h \"Cache-Control: no-cache\" gs://esa-cfs-storage/$npm_package_version/"
"clean:story-packages": "find ./storage -type f -name \"*.zip\" -delete",
"upload-storage": "npm run story-packages && gsutil rsync -r -x \".DS_Store\" ./storage gs://esa-cfs-storage/$npm_package_version && gsutil -m setmeta -r -h \"Cache-Control: no-cache\" gs://esa-cfs-storage/$npm_package_version/ && npm run clean:story-packages"
},
"repository": {
"type": "git",
Expand Down
25 changes: 21 additions & 4 deletions src/scripts/actions/fetch-story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,41 @@ export const FETCH_STORY_ERROR = 'FETCH_STORY_ERROR';

interface FetchStorySuccessAction {
type: typeof FETCH_STORY_SUCCESS;
id: string;
language: string;
story: Story;
}

interface FetchStoryErrorAction {
type: typeof FETCH_STORY_ERROR;
id: string;
message: string;
}

export type FetchStoryActions = FetchStorySuccessAction | FetchStoryErrorAction;

function fetchStorySuccessAction(story: Story) {
export function fetchStorySuccessAction(
storyId: string,
language: string,
story: Story
) {
return {
type: FETCH_STORY_SUCCESS,
id: storyId,
language,
story
};
}

function fetchStoryErrorAction(message: string) {
function fetchStoryErrorAction(
storyId: string,
language: string,
message: string
) {
return {
type: FETCH_STORY_ERROR,
id: storyId,
language,
message
};
}
Expand All @@ -42,8 +57,10 @@ const fetchStory = (id: string) => (
const language = languageSelector(getState());

return fetchStoryApi(id, language)
.then(story => dispatch(fetchStorySuccessAction(story)))
.catch(error => dispatch(fetchStoryErrorAction(error.message)));
.then(story => dispatch(fetchStorySuccessAction(id, language, story)))
.catch(error =>
dispatch(fetchStoryErrorAction(id, language, error.message))
);
};

export default fetchStory;
33 changes: 33 additions & 0 deletions src/scripts/components/download-button/download-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, {FunctionComponent} from 'react';
import {isElectron, downloadUrl, deleteId} from '../../libs/electron/index';

interface Props {
url: string;
id: string;
}

export const DownloadButton: FunctionComponent<Props> = ({url, id}) => {
const onDownload = (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
event.preventDefault();
isElectron() && downloadUrl(url);
};

if (!isElectron()) {
return null;
}

return (
<div>
<button onClick={onDownload}>Download</button>
<button
onClick={event => {
event.stopPropagation();
event.preventDefault();
deleteId(id);
}}>
Delete
</button>
</div>
);
};
8 changes: 7 additions & 1 deletion src/scripts/components/story-content/story-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,32 @@ import cx from 'classnames';

import {StoryMode} from '../../types/story-mode';
import {Slide} from '../../types/story';
import {getStoryMediaUrl} from '../../libs/get-story-media-url';

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

interface Props {
storyId: string;
mode: StoryMode;
slide: Slide;
}

const StoryContent: FunctionComponent<Props> = ({mode, slide}) => {
const StoryContent: FunctionComponent<Props> = ({mode, slide, storyId}) => {
const source = mode === StoryMode.Stories ? slide.text : slide.shortText;

const contentClasses = cx(
styles.content,
mode !== StoryMode.Stories && styles.shortTextContent
);

const transformImageUri = (originalSrc: string) =>
getStoryMediaUrl(storyId, originalSrc);

return (
<div className={contentClasses}>
<ReactMarkdown
source={source}
transformImageUri={transformImageUri}
allowedTypes={[
'heading',
'text',
Expand Down
12 changes: 11 additions & 1 deletion src/scripts/components/story-list-item/story-list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import cx from 'classnames';

import {StoryListItem as StoryListItemType} from '../../types/story-list';
import {StoryMode} from '../../types/story-mode';
import {replaceUrlPlaceholders} from '../../libs/replace-url-placeholders';
import {DownloadButton} from '../download-button/download-button';
import {getStoryMediaUrl} from '../../libs/get-story-media-url';
import config from '../../config/main';

import styles from './story-list-item.styl';

Expand All @@ -25,10 +29,15 @@ const StoryListItemContent: FunctionComponent<Props> = ({
mode === StoryMode.Present && styles.present,
selectedIndex >= 0 && styles.selected
);
const downloadUrl = replaceUrlPlaceholders(config.api.storyOfflinePackage, {
id: story.id
});
const downloadId = `story-${story.id}`;
const imageUrl = getStoryMediaUrl(story.id, story.image);

return (
<div
style={{backgroundImage: `url(${story.image})`}}
style={{backgroundImage: `url(${imageUrl})`}}
className={classes}
onClick={() => mode === StoryMode.Showcase && onSelectStory(story.id)}>
{selectedIndex >= 0 && (
Expand All @@ -37,6 +46,7 @@ const StoryListItemContent: FunctionComponent<Props> = ({
<div className={styles.imageInfo}>
<p className={styles.title}>{story.title}</p>
<p className={styles.description}>{story.description}</p>
<DownloadButton url={downloadUrl} id={downloadId} />
</div>
</div>
);
Expand Down
7 changes: 6 additions & 1 deletion src/scripts/components/story/story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ const Story: FunctionComponent = () => {
{selectedStory?.slides.map(
(currentSlide, index) =>
index === slideIndex && (
<StoryContent mode={mode} slide={currentSlide} key={index} />
<StoryContent
storyId={selectedStory.id}
mode={mode}
slide={currentSlide}
key={index}
/>
)
)}
<Globes />
Expand Down
2 changes: 2 additions & 0 deletions src/scripts/config/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export default {
layer: `https://storage.googleapis.com/esa-cfs-tiles/${version}/{id}/metadata.json`,
layerTiles: `https://storage.googleapis.com/esa-cfs-tiles/${version}/{id}/tiles/{timeIndex}/{z}/{x}/{reverseY}.png`,
layerOfflinePackage: `https://storage.googleapis.com/esa-cfs-tiles/${version}/{id}/package.zip`,
storyOfflinePackage: `https://storage.googleapis.com/esa-cfs-storage/${version}/stories/{id}/package.zip`,
storyMediaBase: `https://storage.googleapis.com/esa-cfs-storage/${version}/stories/{id}`,
stories: `https://storage.googleapis.com/esa-cfs-storage/${version}/stories/stories-{lang}.json`,
story: `https://storage.googleapis.com/esa-cfs-storage/${version}/stories/{id}/{id}-{lang}.json`
},
Expand Down
9 changes: 9 additions & 0 deletions src/scripts/libs/electron/get-offline-story-media-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Returns the url template for offline usage
export function getOfflineStoryMediaUrl(): string {
if (!window.cfs) {
console.error('Calling electron function from a non-electron environment');
return '';
}

return window.cfs.getDownloadsPath('downloads', 'story-{id}');
}
1 change: 1 addition & 0 deletions src/scripts/libs/electron/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ export {deleteId} from './delete-id';
export {downloadUrl} from './download-url';
export {connectToStore} from './connect-to-store';
export {getOfflineTilesUrl} from './get-offline-tiles-url';
export {getOfflineStoryMediaUrl} from './get-offline-story-media-url';
export {offlineSaveMiddleware} from './offline-middleware';
export {offlineLoadMiddleware} from './offline-middleware';
11 changes: 8 additions & 3 deletions src/scripts/libs/electron/offline-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
} from '../../actions/fetch-stories';
import {
FETCH_STORY_SUCCESS,
FETCH_STORY_ERROR
FETCH_STORY_ERROR,
fetchStorySuccessAction
} from '../../actions/fetch-story';
import {
FETCH_LAYER_SUCCESS,
Expand Down Expand Up @@ -40,8 +41,12 @@ const actionsToPersist: ActionToPersist[] = [
{
success: FETCH_STORY_SUCCESS,
error: FETCH_STORY_ERROR,
save: false, // for this action we only want to load the file from the story's offline package
load: true
save: false, // for this action we only want to load the file from the storie's offline package
load: true,
getFilePath: (errorAction: AnyAction) =>
`downloads/story-${errorAction.id}/${errorAction.id}-${errorAction.language}.json`, // the path relative to the app's offline folder
successActionCreator: (errorAction, content) =>
fetchStorySuccessAction(errorAction.id, errorAction.language, content)
},
{
success: FETCH_LAYER_SUCCESS,
Expand Down
22 changes: 22 additions & 0 deletions src/scripts/libs/get-story-media-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
isElectron,
isOffline,
getOfflineStoryMediaUrl
} from '../libs/electron/index';
import {replaceUrlPlaceholders} from '../libs/replace-url-placeholders';
import config from '../config/main';

export function getStoryMediaUrl(
storyId: string,
relativePath: string
): string {
let baseUrl = replaceUrlPlaceholders(config.api.storyMediaBase, {
id: storyId
});

if (isElectron() && isOffline()) {
baseUrl = replaceUrlPlaceholders(getOfflineStoryMediaUrl(), {id: storyId});
}

return `${baseUrl}/${relativePath}`;
}
16 changes: 8 additions & 8 deletions storage/stories/stories-de.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,48 @@
"id": "story1",
"title": "Planetarer Wärmespeicher",
"description": "",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story1/assets/sst_large_18.jpg"
"image": "assets/sst_large_18.jpg"
},
{
"id": "story2",
"title": "Geschichte 2",
"description": "Das ist Geschichte 2",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story2/assets/story2.jpeg"
"image": "assets/story2.jpeg"
},
{
"id": "story3",
"title": "Geschichte 3",
"description": "Das ist Geschichte 3",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story3/assets/story3.jpeg"
"image": "assets/story3.jpeg"
},
{
"id": "story4",
"title": "Geschichte 4",
"description": "Das ist Geschichte 4",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story4/assets/story4.jpeg"
"image": "assets/story4.jpeg"
},
{
"id": "story5",
"title": "Geschichte 5",
"description": "Das ist Geschichte 5",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story5/assets/story5.jpeg"
"image": "assets/story5.jpeg"
},
{
"id": "story6",
"title": "Geschichte 6",
"description": "Das ist Geschichte 6",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story6/assets/story6.jpeg"
"image": "assets/story6.jpeg"
},
{
"id": "story7",
"title": "Geschichte 7",
"description": "Das ist Geschichte 7",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story7/assets/story7.jpeg"
"image": "assets/story7.jpeg"
},
{
"id": "story8",
"title": "Geschichte 8",
"description": "Das ist Geschichte 8",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story8/assets/story8.jpeg"
"image": "assets/story8.jpeg"
}
]
16 changes: 8 additions & 8 deletions storage/stories/stories-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,48 @@
"id": "story1",
"title": "Planetary Heat Store",
"description": "",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story1/assets/sst_large_18.jpg"
"image": "assets/sst_large_18.jpg"
},
{
"id": "story2",
"title": "Story 2",
"description": "This is story 2",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story2/assets/story2.jpeg"
"image": "assets/story2.jpeg"
},
{
"id": "story3",
"title": "Story 3",
"description": "This is story 3",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story3/assets/story3.jpeg"
"image": "assets/story3.jpeg"
},
{
"id": "story4",
"title": "Story 4",
"description": "This is story 4",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story4/assets/story4.jpeg"
"image": "assets/story4.jpeg"
},
{
"id": "story5",
"title": "Story 5",
"description": "This is story 5",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story5/assets/story5.jpeg"
"image": "assets/story5.jpeg"
},
{
"id": "story6",
"title": "Story 6",
"description": "This is story 6",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story6/assets/story6.jpeg"
"image": "assets/story6.jpeg"
},
{
"id": "story7",
"title": "Story 7",
"description": "This is story 7",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story7/assets/story7.jpeg"
"image": "assets/story7.jpeg"
},
{
"id": "story8",
"title": "Story 8",
"description": "This is story 8",
"image": "https://storage.googleapis.com/esa-cfs-storage/stories/story8/assets/story8.jpeg"
"image": "assets/story8.jpeg"
}
]
Loading

0 comments on commit a2f5a02

Please sign in to comment.