add explicit offline mode
This commit is contained in:
parent
90762897db
commit
cbcb270ed3
|
@ -15,6 +15,7 @@ const now = require('performance-now')
|
||||||
|
|
||||||
const globalScss = path.join(__dirname, '../scss/global.scss')
|
const globalScss = path.join(__dirname, '../scss/global.scss')
|
||||||
const defaultThemeScss = path.join(__dirname, '../scss/themes/_default.scss')
|
const defaultThemeScss = path.join(__dirname, '../scss/themes/_default.scss')
|
||||||
|
const offlineThemeScss = path.join(__dirname, '../scss/themes/_offline.scss')
|
||||||
const html2xxFile = path.join(__dirname, '../templates/2xx.html')
|
const html2xxFile = path.join(__dirname, '../templates/2xx.html')
|
||||||
const scssDir = path.join(__dirname, '../scss')
|
const scssDir = path.join(__dirname, '../scss')
|
||||||
const themesScssDir = path.join(__dirname, '../scss/themes')
|
const themesScssDir = path.join(__dirname, '../scss/themes')
|
||||||
|
@ -37,7 +38,8 @@ function doWatch() {
|
||||||
async function compileGlobalSass() {
|
async function compileGlobalSass() {
|
||||||
let results = await Promise.all([
|
let results = await Promise.all([
|
||||||
render({file: defaultThemeScss, outputStyle: 'compressed'}),
|
render({file: defaultThemeScss, outputStyle: 'compressed'}),
|
||||||
render({file: globalScss, outputStyle: 'compressed'})
|
render({file: globalScss, outputStyle: 'compressed'}),
|
||||||
|
render({file: offlineThemeScss, outputStyle: 'compressed'})
|
||||||
])
|
])
|
||||||
|
|
||||||
let css = results.map(_ => _.css).join('')
|
let css = results.map(_ => _.css).join('')
|
||||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -3310,6 +3310,11 @@
|
||||||
"repeating": "2.0.1"
|
"repeating": "2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"indexeddb-getall-shim": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/indexeddb-getall-shim/-/indexeddb-getall-shim-1.3.1.tgz",
|
||||||
|
"integrity": "sha1-aOUV06zspHd+/9DP+ZGS248KXoY="
|
||||||
|
},
|
||||||
"indexes-of": {
|
"indexes-of": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"fg-loadcss": "^2.0.1",
|
"fg-loadcss": "^2.0.1",
|
||||||
"font-awesome-svg-png": "^1.2.2",
|
"font-awesome-svg-png": "^1.2.2",
|
||||||
"glob": "^7.1.2",
|
"glob": "^7.1.2",
|
||||||
|
"indexeddb-getall-shim": "^1.3.1",
|
||||||
"intersection-observer": "^0.5.0",
|
"intersection-observer": "^0.5.0",
|
||||||
"intl-relativeformat": "^2.1.0",
|
"intl-relativeformat": "^2.1.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<:Window bind:innerHeight='innerHeight'/>
|
<:Window bind:innerHeight bind:online />
|
||||||
<Nav page={{page}} />
|
<Nav page={{page}} />
|
||||||
|
|
||||||
<div class="container" on:scroll="onScroll(event)" ref:node>
|
<div class="container" on:scroll="onScroll(event)" ref:node>
|
||||||
|
@ -12,15 +12,27 @@
|
||||||
|
|
||||||
import debounce from 'lodash/debounce'
|
import debounce from 'lodash/debounce'
|
||||||
import throttle from 'lodash/throttle'
|
import throttle from 'lodash/throttle'
|
||||||
|
import { toast } from '../_utils/toast'
|
||||||
import { mark, stop } from '../_utils/marks'
|
import { mark, stop } from '../_utils/marks'
|
||||||
|
|
||||||
const SCROLL_EVENT_DELAY = 300
|
const SCROLL_EVENT_DELAY = 300
|
||||||
const RESIZE_EVENT_DELAY = 700
|
const RESIZE_EVENT_DELAY = 700
|
||||||
|
const OFFLINE_DELAY = 1000
|
||||||
|
|
||||||
|
const notifyOffline = debounce(() => {
|
||||||
|
toast.say('You appear to be offline. You can still read toots while offline.')
|
||||||
|
}, OFFLINE_DELAY)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate() {
|
oncreate() {
|
||||||
mark('onCreate Layout')
|
mark('onCreate Layout')
|
||||||
let node = this.refs.node
|
let node = this.refs.node
|
||||||
|
this.observe('online', online => {
|
||||||
|
document.body.classList.toggle('offline', !online)
|
||||||
|
if (!online) {
|
||||||
|
notifyOffline()
|
||||||
|
}
|
||||||
|
})
|
||||||
this.observe('innerHeight', debounce(() => {
|
this.observe('innerHeight', debounce(() => {
|
||||||
// respond to window resize events
|
// respond to window resize events
|
||||||
this.store.set({
|
this.store.set({
|
||||||
|
|
|
@ -26,14 +26,13 @@
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async oncreate() {
|
async oncreate() {
|
||||||
this.observe('online', e => console.log(e))
|
|
||||||
let instanceName = this.store.get('currentInstance')
|
let instanceName = this.store.get('currentInstance')
|
||||||
let instanceData = this.store.get('currentInstanceData')
|
let instanceData = this.store.get('currentInstanceData')
|
||||||
let online = this.get('online')
|
let online = this.get('online')
|
||||||
let statuses = online ?
|
let statuses = online ?
|
||||||
await getHomeTimeline(instanceName, instanceData.access_token, null, FETCH_LIMIT) :
|
await getHomeTimeline(instanceName, instanceData.access_token, null, FETCH_LIMIT) :
|
||||||
await getTimelineFromDatabaseAfter(null, FETCH_LIMIT)
|
await getTimelineFromDatabaseAfter(null, FETCH_LIMIT)
|
||||||
if (!online) {
|
if (online) {
|
||||||
insertStatusesIntoDatabase(statuses)
|
insertStatusesIntoDatabase(statuses)
|
||||||
}
|
}
|
||||||
this.addStatuses(statuses)
|
this.addStatuses(statuses)
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import { virtualListStore } from '../_utils/virtualListStore'
|
import { virtualListStore } from '../_utils/virtualListStore'
|
||||||
import { mark, stop } from '../_utils/marks'
|
import { mark, stop } from '../_utils/marks'
|
||||||
|
|
||||||
const DISTANCE_FROM_BOTTOM_TO_FIRE = 400
|
const DISTANCE_FROM_BOTTOM_TO_FIRE = 200
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
|
|
|
@ -21,9 +21,14 @@ const importRequestIdleCallback = () => import(
|
||||||
/* webpackChunkName: 'requestidlecallback' */ 'requestidlecallback'
|
/* webpackChunkName: 'requestidlecallback' */ 'requestidlecallback'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const importIndexedDBGetAllShim = () => import(
|
||||||
|
/* webpackChunkName: 'indexeddb-getall-shim' */ 'indexeddb-getall-shim'
|
||||||
|
)
|
||||||
|
|
||||||
export {
|
export {
|
||||||
importURLSearchParams,
|
importURLSearchParams,
|
||||||
importTimeline,
|
importTimeline,
|
||||||
importIntersectionObserver,
|
importIntersectionObserver,
|
||||||
importRequestIdleCallback
|
importRequestIdleCallback,
|
||||||
|
importIndexedDBGetAllShim
|
||||||
}
|
}
|
|
@ -22,8 +22,8 @@ const dbPromise = new Promise((resolve, reject) => {
|
||||||
|
|
||||||
function transformStatusForStorage(status) {
|
function transformStatusForStorage(status) {
|
||||||
status = cloneDeep(status)
|
status = cloneDeep(status)
|
||||||
status.pinafore_id_as_big_int = parseInt(status, 10)
|
status.pinafore_id_as_big_int = parseInt(status.id, 10)
|
||||||
status.pinafore_id_as_negative_big_int = -parseInt(status, 10)
|
status.pinafore_id_as_negative_big_int = -parseInt(status.id, 10)
|
||||||
status.pinafore_stale = true
|
status.pinafore_stale = true
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
@ -34,19 +34,15 @@ export async function getTimelineAfter(max_id = null, limit = 20) {
|
||||||
const tx = db.transaction(STORE, 'readonly')
|
const tx = db.transaction(STORE, 'readonly')
|
||||||
const store = tx.objectStore(STORE)
|
const store = tx.objectStore(STORE)
|
||||||
const index = store.index('pinafore_id_as_negative_big_int')
|
const index = store.index('pinafore_id_as_negative_big_int')
|
||||||
let res
|
|
||||||
let sinceAsNegativeBigInt = max_id === null ? null : -parseInt(max_id, 10)
|
let sinceAsNegativeBigInt = max_id === null ? null : -parseInt(max_id, 10)
|
||||||
let query = sinceAsNegativeBigInt === null ? null : IDBKeyRange.lowerBound(sinceAsNegativeBigInt, false)
|
let query = sinceAsNegativeBigInt === null ? null : IDBKeyRange.lowerBound(sinceAsNegativeBigInt, false)
|
||||||
|
|
||||||
|
let res
|
||||||
index.getAll(query, limit).onsuccess = (e) => {
|
index.getAll(query, limit).onsuccess = (e) => {
|
||||||
console.log('done calling getAll()')
|
|
||||||
res = e.target.result
|
res = e.target.result
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.oncomplete = () => {
|
tx.oncomplete = () => resolve(res)
|
||||||
console.log('complete')
|
|
||||||
resolve(res)
|
|
||||||
}
|
|
||||||
tx.onerror = reject
|
tx.onerror = reject
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,8 @@ $secondary-text-color: white;
|
||||||
$toast-border: #fafafa;
|
$toast-border: #fafafa;
|
||||||
$toast-bg: #333;
|
$toast-bg: #333;
|
||||||
|
|
||||||
$whatever: royalblue;
|
|
||||||
|
|
||||||
@import "_base.scss";
|
@import "_base.scss";
|
||||||
|
|
||||||
:root {
|
body {
|
||||||
@include baseTheme();
|
@include baseTheme();
|
||||||
}
|
}
|
||||||
|
|
20
scss/themes/_offline.scss
Normal file
20
scss/themes/_offline.scss
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
$main-theme-color: #999999;
|
||||||
|
$body-bg-color: lighten($main-theme-color, 38%);
|
||||||
|
$anchor-color: $main-theme-color;
|
||||||
|
$main-text-color: #333;
|
||||||
|
$border-color: #dadada;
|
||||||
|
$main-bg-color: white;
|
||||||
|
$secondary-text-color: white;
|
||||||
|
$toast-border: #fafafa;
|
||||||
|
$toast-bg: #333;
|
||||||
|
|
||||||
|
@import "_base.scss";
|
||||||
|
|
||||||
|
body.offline,
|
||||||
|
body.theme-hotpants.offline,
|
||||||
|
body.theme-majesty.offline,
|
||||||
|
body.theme-oaken.offline,
|
||||||
|
body.theme-scarlet.offline,
|
||||||
|
body.theme-seafoam.offline {
|
||||||
|
@include baseTheme();
|
||||||
|
}
|
|
@ -16,8 +16,9 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* auto-generated w/ build-sass.js */
|
/* auto-generated w/ build-sass.js */
|
||||||
:root{--button-primary-bg:#6081e6;--button-primary-text:#fff;--button-primary-border:#132c76;--button-primary-bg-active:#456ce2;--button-primary-bg-hover:#6988e7;--button-bg:#e6e6e6;--button-text:#333;--button-border:#a7a7a7;--button-bg-active:#bfbfbf;--button-bg-hover:#f2f2f2;--input-border:#dadada;--anchor-text:#4169e1;--main-bg:#fff;--body-bg:#e8edfb;--body-text-color:#333;--main-border:#dadada;--svg-fill:#4169e1;--form-bg:#f7f7f7;--form-border:#c1c1c1;--nav-bg:#4169e1;--nav-border:#214cce;--nav-a-border:#4169e1;--nav-a-selected-border:#fff;--nav-a-selected-bg:#6d8ce8;--nav-svg-fill:#fff;--nav-text-color:#fff;--nav-a-selected-border-hover:#fff;--nav-a-selected-bg-hover:#839deb;--nav-a-bg-hover:#577ae4;--nav-a-border-hover:#4169e1;--nav-svg-fill-hover:#fff;--nav-text-color-hover:#fff;--action-button-fill-color:#839deb;--action-button-fill-color-hover:#99afef;--action-button-fill-color-active:#577ae4;--settings-list-item-bg:#fff;--settings-list-item-text:#4169e1;--settings-list-item-text-hover:#4169e1;--settings-list-item-border:#dadada;--settings-list-item-bg-active:#e6e6e6;--settings-list-item-bg-hover:#fafafa;--toast-bg:#333;--toast-border:#fafafa;--toast-text:#fff;--mask-bg:#333;--mask-svg-fill:#fff;--deemphasized-text-color:#666}
|
body{--button-primary-bg:#6081e6;--button-primary-text:#fff;--button-primary-border:#132c76;--button-primary-bg-active:#456ce2;--button-primary-bg-hover:#6988e7;--button-bg:#e6e6e6;--button-text:#333;--button-border:#a7a7a7;--button-bg-active:#bfbfbf;--button-bg-hover:#f2f2f2;--input-border:#dadada;--anchor-text:#4169e1;--main-bg:#fff;--body-bg:#e8edfb;--body-text-color:#333;--main-border:#dadada;--svg-fill:#4169e1;--form-bg:#f7f7f7;--form-border:#c1c1c1;--nav-bg:#4169e1;--nav-border:#214cce;--nav-a-border:#4169e1;--nav-a-selected-border:#fff;--nav-a-selected-bg:#6d8ce8;--nav-svg-fill:#fff;--nav-text-color:#fff;--nav-a-selected-border-hover:#fff;--nav-a-selected-bg-hover:#839deb;--nav-a-bg-hover:#577ae4;--nav-a-border-hover:#4169e1;--nav-svg-fill-hover:#fff;--nav-text-color-hover:#fff;--action-button-fill-color:#839deb;--action-button-fill-color-hover:#99afef;--action-button-fill-color-active:#577ae4;--settings-list-item-bg:#fff;--settings-list-item-text:#4169e1;--settings-list-item-text-hover:#4169e1;--settings-list-item-border:#dadada;--settings-list-item-bg-active:#e6e6e6;--settings-list-item-bg-hover:#fafafa;--toast-bg:#333;--toast-border:#fafafa;--toast-text:#fff;--mask-bg:#333;--mask-svg-fill:#fff;--deemphasized-text-color:#666}
|
||||||
body{margin:0;font-family:system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue;font-size:14px;line-height:1.3;color:var(--body-text-color);background:var(--body-bg);position:fixed;left:0;right:0;bottom:0;top:0}.container{overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;position:absolute;top:72px;left:0;right:0;bottom:0;will-change:transform}main{position:relative;width:602px;max-width:100vw;padding:15px 0;box-sizing:border-box;margin:15px auto 15px;background:var(--main-bg);border:1px solid var(--main-border);border-radius:1px}@media (max-width: 767px){main{margin:5px auto 15px}}h1,h2,h3,h4,h5,h6{margin:0 0 0.5em 0;font-weight:400;line-height:1.2}h1{font-size:2em}a{color:var(--anchor-text);text-decoration:none}a:visited{color:var(--anchor-text)}a:hover{text-decoration:underline}input{border:1px solid var(--input-border);padding:5px}button{font-size:1.2em;background:var(--button-bg);border-radius:2px;padding:10px 15px;border:1px solid var(--button-border);cursor:pointer;color:var(--button-text)}button:hover{background:var(--button-bg-hover)}button:active{background:var(--button-bg-active)}button[disabled]{opacity:0.35;pointer-events:none;cursor:not-allowed}button.primary{border:1px solid var(--button-primary-border);background:var(--button-primary-bg);color:var(--button-primary-text)}button.primary:hover{background:var(--button-primary-bg-hover)}button.primary:active{background:var(--button-primary-bg-active)}p,label,input{font-size:1.3em}ul,li,p{padding:0;margin:0}.hidden{opacity:0}
|
body{margin:0;font-family:system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue;font-size:14px;line-height:1.3;color:var(--body-text-color);background:var(--body-bg);position:fixed;left:0;right:0;bottom:0;top:0}.container{overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;position:absolute;top:72px;left:0;right:0;bottom:0;will-change:transform}main{position:relative;width:602px;max-width:100vw;padding:15px 0;box-sizing:border-box;margin:15px auto 15px;background:var(--main-bg);border:1px solid var(--main-border);border-radius:1px}@media (max-width: 767px){main{margin:5px auto 15px}}h1,h2,h3,h4,h5,h6{margin:0 0 0.5em 0;font-weight:400;line-height:1.2}h1{font-size:2em}a{color:var(--anchor-text);text-decoration:none}a:visited{color:var(--anchor-text)}a:hover{text-decoration:underline}input{border:1px solid var(--input-border);padding:5px}button{font-size:1.2em;background:var(--button-bg);border-radius:2px;padding:10px 15px;border:1px solid var(--button-border);cursor:pointer;color:var(--button-text)}button:hover{background:var(--button-bg-hover)}button:active{background:var(--button-bg-active)}button[disabled]{opacity:0.35;pointer-events:none;cursor:not-allowed}button.primary{border:1px solid var(--button-primary-border);background:var(--button-primary-bg);color:var(--button-primary-text)}button.primary:hover{background:var(--button-primary-bg-hover)}button.primary:active{background:var(--button-primary-bg-active)}p,label,input{font-size:1.3em}ul,li,p{padding:0;margin:0}.hidden{opacity:0}
|
||||||
|
body.offline,body.theme-hotpants.offline,body.theme-majesty.offline,body.theme-oaken.offline,body.theme-scarlet.offline,body.theme-seafoam.offline{--button-primary-bg:#ababab;--button-primary-text:#fff;--button-primary-border:#4d4d4d;--button-primary-bg-active:#9c9c9c;--button-primary-bg-hover:#b0b0b0;--button-bg:#e6e6e6;--button-text:#333;--button-border:#a7a7a7;--button-bg-active:#bfbfbf;--button-bg-hover:#f2f2f2;--input-border:#dadada;--anchor-text:#999;--main-bg:#fff;--body-bg:#fafafa;--body-text-color:#333;--main-border:#dadada;--svg-fill:#999;--form-bg:#f7f7f7;--form-border:#c1c1c1;--nav-bg:#999;--nav-border:gray;--nav-a-border:#999;--nav-a-selected-border:#fff;--nav-a-selected-bg:#b3b3b3;--nav-svg-fill:#fff;--nav-text-color:#fff;--nav-a-selected-border-hover:#fff;--nav-a-selected-bg-hover:#bfbfbf;--nav-a-bg-hover:#a6a6a6;--nav-a-border-hover:#999;--nav-svg-fill-hover:#fff;--nav-text-color-hover:#fff;--action-button-fill-color:#bfbfbf;--action-button-fill-color-hover:#ccc;--action-button-fill-color-active:#a6a6a6;--settings-list-item-bg:#fff;--settings-list-item-text:#999;--settings-list-item-text-hover:#999;--settings-list-item-border:#dadada;--settings-list-item-bg-active:#e6e6e6;--settings-list-item-bg-hover:#fafafa;--toast-bg:#333;--toast-border:#fafafa;--toast-text:#fff;--mask-bg:#333;--mask-svg-fill:#fff;--deemphasized-text-color:#666}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { init } from 'sapper/runtime.js'
|
import { init } from 'sapper/runtime.js'
|
||||||
|
import { toast } from '../routes/_utils/toast'
|
||||||
import {
|
import {
|
||||||
importURLSearchParams,
|
importURLSearchParams,
|
||||||
importIntersectionObserver,
|
importIntersectionObserver,
|
||||||
importRequestIdleCallback
|
importRequestIdleCallback,
|
||||||
|
importIndexedDBGetAllShim,
|
||||||
} from '../routes/_utils/asyncModules'
|
} from '../routes/_utils/asyncModules'
|
||||||
|
|
||||||
// polyfills
|
// polyfills
|
||||||
Promise.all([
|
Promise.all([
|
||||||
typeof URLSearchParams === 'undefined' && importURLSearchParams(),
|
typeof URLSearchParams === 'undefined' && importURLSearchParams(),
|
||||||
typeof IntersectionObserver === 'undefined' && importIntersectionObserver(),
|
typeof IntersectionObserver === 'undefined' && importIntersectionObserver(),
|
||||||
typeof requestIdleCallback === 'undefined' && importRequestIdleCallback()
|
typeof requestIdleCallback === 'undefined' && importRequestIdleCallback(),
|
||||||
|
!IDBObjectStore.prototype.getAll && importIndexedDBGetAllShim()
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
// `routes` is an array of route objects injected by Sapper
|
// `routes` is an array of route objects injected by Sapper
|
||||||
init(document.querySelector('#sapper'), __routes__)
|
init(document.querySelector('#sapper'), __routes__)
|
||||||
|
@ -17,7 +20,7 @@ Promise.all([
|
||||||
if (navigator.serviceWorker && navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker && navigator.serviceWorker.controller) {
|
||||||
navigator.serviceWorker.controller.onstatechange = (e) => {
|
navigator.serviceWorker.controller.onstatechange = (e) => {
|
||||||
if (e.target.state === 'redundant') {
|
if (e.target.state === 'redundant') {
|
||||||
importToast().then(toast => toast.say('App update available. Reload to update.'));
|
toast.say('App update available. Reload to update.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,8 @@ self.addEventListener('activate', event => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const NETWORK_ONLY = [
|
const NETWORK_ONLY = [
|
||||||
'/oauth'
|
'/oauth',
|
||||||
|
'/api/v1/timelines'
|
||||||
]
|
]
|
||||||
|
|
||||||
const CACHE_FIRST = [
|
const CACHE_FIRST = [
|
||||||
|
|
Loading…
Reference in a new issue