Skip to content

Commit 3aba35e

Browse files
committed
Merge pull request #36 from insin/offline-mode
Introduce Offline Mode
2 parents 37f709c + b81ecaf commit 3aba35e

File tree

12 files changed

+203
-29
lines changed

12 files changed

+203
-29
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
},
1111
"scripts": {
1212
"build": "npm run lint && cp node_modules/sw-toolbox/sw-toolbox.js public/sw-toolbox.js && nwb build && npm run precache",
13-
"lint": "eslint src",
14-
"lint:fix": "eslint --fix .",
13+
"lint": "./node_modules/eslint-config-jonnybuchanan/bin/lint.js src",
14+
"lint:fix": "./node_modules/eslint-config-jonnybuchanan/bin/lint.js --fix .",
1515
"start": "nwb serve",
1616
"precache": "sw-precache --root=public --config=sw-precache-config.json"
1717
},
@@ -20,6 +20,7 @@
2020
"firebase": "2.4.2",
2121
"firetruck.js": "0.1.1",
2222
"history": "2.1.1",
23+
"isomorphic-fetch": "^2.2.1",
2324
"react": "15.0.2",
2425
"react-dom": "15.0.2",
2526
"react-router": "2.4.0",

public/runtime-caching.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
origin: /\.(?:googleapis|gstatic|firebaseio)\.com$/
1313
})
1414
global.toolbox.router.get('/(.+)', global.toolbox.fastest, {
15-
origin: 'https://hacker-news.firebaseio.com'
15+
origin: 'https://hacker-news.firebaseio.com'
1616
})
1717
global.toolbox.router.get('/(.+)', global.toolbox.fastest, {
18-
origin: 'https://s-usc1c-nss-136.firebaseio.com'
18+
origin: 'https://s-usc1c-nss-136.firebaseio.com'
1919
})
2020
})(self)
2121

src/Comment.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var ReactFireMixin = require('reactfire')
33

44
var CommentThreadStore = require('./stores/CommentThreadStore')
55
var HNService = require('./services/HNService')
6+
var HNServiceRest = require('./services/HNServiceRest')
67
var SettingsStore = require('./stores/SettingsStore')
78

89
var CommentMixin = require('./mixins/CommentMixin')
@@ -86,7 +87,17 @@ var Comment = React.createClass({
8687
},
8788

8889
bindFirebaseRef() {
89-
this.bindAsObject(HNService.itemRef(this.props.id), 'comment', this.handleFirebaseRefCancelled)
90+
if (SettingsStore.offlineMode) {
91+
HNServiceRest.itemRef(this.props.id).then(function(res) {
92+
return res.json()
93+
}).then(function(snapshot) {
94+
this.replaceState({ comment: snapshot })
95+
}.bind(this))
96+
}
97+
else {
98+
this.bindAsObject(HNService.itemRef(this.props.id), 'comment', this.handleFirebaseRefCancelled)
99+
}
100+
90101
if (this.timeout) {
91102
this.timeout = null
92103
}

src/Item.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var ReactFireMixin = require('reactfire')
33
var TimeAgo = require('react-timeago').default
44

55
var HNService = require('./services/HNService')
6+
var HNServiceRest = require('./services/HNServiceRest')
67
var StoryCommentThreadStore = require('./stores/StoryCommentThreadStore')
78
var ItemStore = require('./stores/ItemStore')
89

@@ -14,6 +15,8 @@ var ItemMixin = require('./mixins/ItemMixin')
1415
var cx = require('./utils/buildClassName')
1516
var setTitle = require('./utils/setTitle')
1617

18+
var SettingsStore = require('./stores/SettingsStore')
19+
1720
function timeUnitsAgo(value, unit, suffix) {
1821
if (value === 1) {
1922
return unit
@@ -31,7 +34,17 @@ var Item = React.createClass({
3134
},
3235

3336
componentWillMount() {
34-
this.bindAsObject(HNService.itemRef(this.props.params.id), 'item')
37+
if (SettingsStore.offlineMode) {
38+
HNServiceRest.itemRef(this.props.params.id).then(function(res) {
39+
return res.json()
40+
}).then(function(snapshot) {
41+
this.replaceState({ item: snapshot })
42+
}.bind(this))
43+
}
44+
else {
45+
this.bindAsObject(HNService.itemRef(this.props.params.id), 'item')
46+
}
47+
3548
if (this.state.item.id) {
3649
this.threadStore = new StoryCommentThreadStore(this.state.item, this.handleCommentsChanged, {cached: true})
3750
setTitle(this.state.item.title)
@@ -58,8 +71,18 @@ var Item = React.createClass({
5871
this.threadStore = new StoryCommentThreadStore(item, this.handleCommentsChanged, {cached: true})
5972
setTitle(item.title)
6073
}
61-
this.bindAsObject(HNService.itemRef(nextProps.params.id), 'item')
62-
this.setState({item: item || {}})
74+
75+
if (SettingsStore.offlineMode) {
76+
HNServiceRest.itemRef(nextProps.params.id).then(function(res) {
77+
return res.json()
78+
}).then(function(snapshot) {
79+
this.replaceState({ item: snapshot })
80+
}.bind(this))
81+
}
82+
else {
83+
this.bindAsObject(HNService.itemRef(nextProps.params.id), 'item')
84+
this.setState({item: item || {}})
85+
}
6386
}
6487
},
6588

src/PermalinkedComment.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var withRouter = require('react-router/lib/withRouter')
44

55
var CommentThreadStore = require('./stores/CommentThreadStore')
66
var HNService = require('./services/HNService')
7+
var HNServiceRest = require('./services/HNServiceRest')
78
var SettingsStore = require('./stores/SettingsStore')
89
var UpdatesStore = require('./stores/UpdatesStore')
910

@@ -32,7 +33,16 @@ var PermalinkedComment = React.createClass({
3233
},
3334

3435
componentWillMount() {
35-
this.bindAsObject(HNService.itemRef(this.props.params.id), 'comment')
36+
if (SettingsStore.offlineMode) {
37+
HNServiceRest.itemRef(this.props.params.id).then(function(res) {
38+
return res.json()
39+
}).then(function(snapshot) {
40+
this.replaceState({ comment: snapshot })
41+
}.bind(this))
42+
}
43+
else {
44+
this.bindAsObject(HNService.itemRef(this.props.params.id), 'comment')
45+
}
3646
if (this.state.comment.id) {
3747
this.commentLoaded(this.state.comment)
3848
}

src/Settings.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ var Settings = React.createClass({
3838
</label>
3939
<p>Show "reply" links to Hacker News</p>
4040
</div>
41+
<div className="Settings__setting Settings__setting--checkbox">
42+
<label htmlFor="offlineMode">
43+
<input type="checkbox" name="offlineMode" id="offlineMode" checked={SettingsStore.offlineMode}/> Offline Mode
44+
</label>
45+
<p>Cache comments and content offline.</p>
46+
</div>
4147
<div className="Settings__setting Settings__setting--checkbox">
4248
<label htmlFor="showDead">
4349
<input type="checkbox" name="showDead" id="showDead" checked={SettingsStore.showDead}/> show dead

src/StoryListItem.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var ReactFireMixin = require('reactfire')
33

44
var StoryCommentThreadStore = require('./stores/StoryCommentThreadStore')
55
var HNService = require('./services/HNService')
6+
var HNServiceRest = require('./services/HNServiceRest')
67
var SettingsStore = require('./stores/SettingsStore')
78
var StoryStore = require('./stores/StoryStore')
89

@@ -94,8 +95,18 @@ var StoryListItem = React.createClass({
9495
* initialise its comment thread state.
9596
*/
9697
initLiveItem(props) {
97-
// If we were given a cached item to display initially, it will be replaced
98-
this.bindAsObject(HNService.itemRef(props.id), 'item')
98+
if (SettingsStore.offlineMode) {
99+
HNServiceRest.itemRef(props.id).then(function(res) {
100+
return res.json()
101+
}).then(function(snapshot) {
102+
this.replaceState({ item: snapshot })
103+
}.bind(this))
104+
}
105+
else {
106+
// If we were given a cached item to display initially, it will be replaced
107+
this.bindAsObject(HNService.itemRef(props.id), 'item')
108+
}
109+
99110
this.threadState = StoryCommentThreadStore.loadState(props.id)
100111
this.props.store.addListener(props.id, this.updateThreadState)
101112
},

src/services/HNServiceRest.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*global fetch*/
2+
require('isomorphic-fetch')
3+
/*
4+
A version of HNService which concumes the Firebase REST
5+
endpoint (https://www.firebase.com/docs/rest/api/). This
6+
is used when a user has enabled 'Offline Mode' in the
7+
Settings panel and ensures responses can be easily fetched
8+
and cached when paired with Service Worker. This cannot be
9+
trivially done using just Web Sockets with the default
10+
Firebase API and provides a sufficient fallback that works.
11+
*/
12+
var endPoint = 'https://hacker-news.firebaseio.com/v0'
13+
var options = {
14+
method: 'GET',
15+
headers: {
16+
'Accept': 'application/json'
17+
}
18+
}
19+
20+
function storiesRef(path) {
21+
return fetch(endPoint + '/' + path + '.json', options)
22+
}
23+
24+
function itemRef(id) {
25+
return fetch(endPoint + '/item/' + id + '.json', options)
26+
}
27+
28+
function userRef(id) {
29+
return fetch(endPoint + '/user/' + id + '.json', options)
30+
}
31+
32+
function updatesRef() {
33+
return fetch(endPoint + '/updates/items/' + '.json', options)
34+
}
35+
36+
function fetchItem(id, cb) {
37+
itemRef(id).once('value', function(snapshot) {
38+
cb(snapshot.val())
39+
})
40+
}
41+
42+
function fetchItems(ids, cb) {
43+
var items = []
44+
ids.forEach(function(id) {
45+
fetchItem(id, addItem)
46+
})
47+
function addItem(item) {
48+
items.push(item)
49+
if (items.length >= ids.length) {
50+
cb(items)
51+
}
52+
}
53+
}
54+
55+
module.exports = {
56+
fetchItem,
57+
fetchItems,
58+
storiesRef,
59+
itemRef,
60+
userRef,
61+
updatesRef
62+
}

src/stores/ItemStore.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
var HNService = require('../services/HNService')
2+
var HNServiceRest = require('../services/HNServiceRest')
23

34
var StoryStore = require('./StoryStore')
45
var UpdatesStore = require('./UpdatesStore')
5-
6+
var SettingsStore = require('./SettingsStore')
67
var commentParentLookup = {}
78
var titleCache = {}
89

@@ -72,7 +73,12 @@ var ItemStore = {
7273
setImmediate(cb, cachedItem)
7374
}
7475
else {
75-
HNService.fetchItem(id, cb)
76+
if (SettingsStore.offlineMode) {
77+
HNServiceRest.fetchItem(id, cb)
78+
}
79+
else {
80+
HNService.fetchItem(id, cb)
81+
}
7682
}
7783
},
7884

src/stores/SettingsStore.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ var SettingsStore = {
1010
showDeleted: false,
1111
titleFontSize: 18,
1212
listSpacing: 16,
13+
offlineMode: false,
1314

1415
load() {
1516
var json = storage.get(STORAGE_KEY)
@@ -25,7 +26,8 @@ var SettingsStore = {
2526
showDead: this.showDead,
2627
showDeleted: this.showDeleted,
2728
titleFontSize: this.titleFontSize,
28-
listSpacing: this.listSpacing
29+
listSpacing: this.listSpacing,
30+
offlineMode: this.offlineMode
2931
}))
3032
}
3133
}

0 commit comments

Comments
 (0)