forked from cybrespace/pinafore
first stab at online mode
This commit is contained in:
parent
1c354817a6
commit
90762897db
|
@ -3282,11 +3282,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"idb": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/idb/-/idb-2.0.4.tgz",
|
||||
"integrity": "sha512-Nw4ykKrrVje6YODRiRm/k2ucFEQeoY+zrkszfOuzVmxx8yyBMtZh2KLaRCKk9r5GzhuF0QlNCVjBewP2n5OZ7Q=="
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
"fg-loadcss": "^2.0.1",
|
||||
"font-awesome-svg-png": "^1.2.2",
|
||||
"glob": "^7.1.2",
|
||||
"idb": "^2.0.4",
|
||||
"intersection-observer": "^0.5.0",
|
||||
"intl-relativeformat": "^2.1.0",
|
||||
"lodash": "^4.17.4",
|
||||
|
|
|
@ -20,16 +20,17 @@
|
|||
export default {
|
||||
oncreate() {
|
||||
mark('onCreate Layout')
|
||||
let node = this.refs.node
|
||||
this.observe('innerHeight', debounce(() => {
|
||||
// respond to window resize events
|
||||
this.store.set({
|
||||
offsetHeight: this.refs.node.offsetHeight
|
||||
offsetHeight: node.offsetHeight
|
||||
})
|
||||
}, RESIZE_EVENT_DELAY))
|
||||
this.store.set({
|
||||
scrollTop: this.refs.node.scrollTop,
|
||||
scrollHeight: this.refs.node.scrollHeight,
|
||||
offsetHeight: this.refs.node.offsetHeight
|
||||
scrollTop: node.scrollTop,
|
||||
scrollHeight: node.scrollHeight,
|
||||
offsetHeight: node.offsetHeight
|
||||
})
|
||||
stop('onCreate Layout')
|
||||
},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<:Window bind:online />
|
||||
<div class="timeline" role="feed" aria-label="{{label}}">
|
||||
<VirtualList component="{{StatusListItem}}"
|
||||
items="{{keyedStatuses}}"
|
||||
|
@ -10,20 +11,32 @@
|
|||
</style>
|
||||
<script>
|
||||
import { store } from '../_utils/store'
|
||||
import { getHomeTimeline } from '../_utils/mastodon/oauth'
|
||||
import { getHomeTimeline } from '../_utils/mastodon/timelines'
|
||||
import StatusListItem from './StatusListItem.html'
|
||||
import VirtualList from './VirtualList.html'
|
||||
import { splice, push } from 'svelte-extras'
|
||||
import {
|
||||
insertStatuses as insertStatusesIntoDatabase,
|
||||
getTimelineAfter as getTimelineFromDatabaseAfter
|
||||
} from '../_utils/database/statuses'
|
||||
import { mergeStatuses } from '../_utils/statuses'
|
||||
import { mark, stop } from '../_utils/marks'
|
||||
|
||||
const FETCH_LIMIT = 20
|
||||
|
||||
export default {
|
||||
async oncreate() {
|
||||
this.observe('online', e => console.log(e))
|
||||
let instanceName = this.store.get('currentInstance')
|
||||
let instanceData = this.store.get('currentInstanceData')
|
||||
let statuses = await getHomeTimeline(instanceName, instanceData.access_token, null, 10)
|
||||
this.set({
|
||||
statuses: statuses,
|
||||
})
|
||||
let online = this.get('online')
|
||||
let statuses = online ?
|
||||
await getHomeTimeline(instanceName, instanceData.access_token, null, FETCH_LIMIT) :
|
||||
await getTimelineFromDatabaseAfter(null, FETCH_LIMIT)
|
||||
if (!online) {
|
||||
insertStatusesIntoDatabase(statuses)
|
||||
}
|
||||
this.addStatuses(statuses)
|
||||
},
|
||||
data: () => ({
|
||||
target: 'home',
|
||||
|
@ -55,16 +68,30 @@
|
|||
let lastStatusId = this.get('lastStatusId')
|
||||
let instanceName = this.store.get('currentInstance')
|
||||
let instanceData = this.store.get('currentInstanceData')
|
||||
let newStatuses = await getHomeTimeline(instanceName, instanceData.access_token, lastStatusId, 10)
|
||||
let online = this.get('online')
|
||||
let newStatuses = online ?
|
||||
await getHomeTimeline(instanceName, instanceData.access_token, lastStatusId, FETCH_LIMIT) :
|
||||
await getTimelineFromDatabaseAfter(lastStatusId, FETCH_LIMIT)
|
||||
if (online) {
|
||||
insertStatusesIntoDatabase(newStatuses)
|
||||
}
|
||||
let statuses = this.get('statuses')
|
||||
if (statuses) {
|
||||
this.addItems(newStatuses)
|
||||
this.addStatuses(newStatuses)
|
||||
}
|
||||
this.set({ runningUpdate: false })
|
||||
stop('onScrollToBottom')
|
||||
},
|
||||
addItems(items) {
|
||||
this.splice('statuses', this.get('statuses').length, 0, ...items)
|
||||
addStatuses(newStatuses) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('addStatuses()')
|
||||
}
|
||||
let statuses = this.get('statuses')
|
||||
if (!statuses) {
|
||||
return
|
||||
}
|
||||
let merged = mergeStatuses(statuses, newStatuses)
|
||||
this.set({ statuses: merged })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
import keyval from './idb-keyval'
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
import idb from 'idb'
|
||||
|
||||
// copypasta'd from https://github.com/jakearchibald/idb#keyval-store
|
||||
|
||||
const dbPromise = idb.open('keyval-store', 1, upgradeDB => {
|
||||
upgradeDB.createObjectStore('keyval')
|
||||
})
|
||||
|
||||
const idbKeyval = {
|
||||
get(key) {
|
||||
return dbPromise.then(db => {
|
||||
return db.transaction('keyval').objectStore('keyval').get(key)
|
||||
})
|
||||
},
|
||||
set(key, val) {
|
||||
return dbPromise.then(db => {
|
||||
const tx = db.transaction('keyval', 'readwrite')
|
||||
tx.objectStore('keyval').put(val, key)
|
||||
return tx.complete
|
||||
})
|
||||
},
|
||||
delete(key) {
|
||||
return dbPromise.then(db => {
|
||||
const tx = db.transaction('keyval', 'readwrite')
|
||||
tx.objectStore('keyval').delete(key)
|
||||
return tx.complete
|
||||
})
|
||||
},
|
||||
clear() {
|
||||
return dbPromise.then(db => {
|
||||
const tx = db.transaction('keyval', 'readwrite')
|
||||
tx.objectStore('keyval').clear()
|
||||
return tx.complete
|
||||
})
|
||||
},
|
||||
keys() {
|
||||
return dbPromise.then(db => {
|
||||
const tx = db.transaction('keyval')
|
||||
const keys = []
|
||||
const store = tx.objectStore('keyval')
|
||||
// This would be store.getAllKeys(), but it isn't supported by Edge or Safari.
|
||||
// openKeyCursor isn't supported by Safari, so we fall back
|
||||
const iterate = store.iterateKeyCursor || store.iterateCursor
|
||||
iterate.call(store, cursor => {
|
||||
if (!cursor) {
|
||||
return
|
||||
}
|
||||
keys.push(cursor.key)
|
||||
cursor.continue()
|
||||
})
|
||||
|
||||
return tx.complete.then(() => keys)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default idbKeyval
|
|
@ -0,0 +1,65 @@
|
|||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
const STORE = 'statuses'
|
||||
|
||||
const dbPromise = new Promise((resolve, reject) => {
|
||||
let req = indexedDB.open(STORE, 1)
|
||||
req.onerror = reject
|
||||
req.onblocked = () => {
|
||||
console.log('idb blocked')
|
||||
}
|
||||
req.onupgradeneeded = () => {
|
||||
let db = req.result;
|
||||
let oStore = db.createObjectStore(STORE, {
|
||||
keyPath: 'id'
|
||||
})
|
||||
oStore.createIndex('created_at', 'created_at')
|
||||
oStore.createIndex('pinafore_id_as_negative_big_int', 'pinafore_id_as_negative_big_int')
|
||||
oStore.createIndex('pinafore_id_as_big_int', 'pinafore_id_as_big_int')
|
||||
}
|
||||
req.onsuccess = () => resolve(req.result)
|
||||
})
|
||||
|
||||
function transformStatusForStorage(status) {
|
||||
status = cloneDeep(status)
|
||||
status.pinafore_id_as_big_int = parseInt(status, 10)
|
||||
status.pinafore_id_as_negative_big_int = -parseInt(status, 10)
|
||||
status.pinafore_stale = true
|
||||
return status
|
||||
}
|
||||
|
||||
export async function getTimelineAfter(max_id = null, limit = 20) {
|
||||
const db = await dbPromise
|
||||
return await new Promise((resolve, reject) => {
|
||||
const tx = db.transaction(STORE, 'readonly')
|
||||
const store = tx.objectStore(STORE)
|
||||
const index = store.index('pinafore_id_as_negative_big_int')
|
||||
let res
|
||||
let sinceAsNegativeBigInt = max_id === null ? null : -parseInt(max_id, 10)
|
||||
let query = sinceAsNegativeBigInt === null ? null : IDBKeyRange.lowerBound(sinceAsNegativeBigInt, false)
|
||||
|
||||
index.getAll(query, limit).onsuccess = (e) => {
|
||||
console.log('done calling getAll()')
|
||||
res = e.target.result
|
||||
}
|
||||
|
||||
tx.oncomplete = () => {
|
||||
console.log('complete')
|
||||
resolve(res)
|
||||
}
|
||||
tx.onerror = reject
|
||||
})
|
||||
}
|
||||
|
||||
export async function insertStatuses(statuses) {
|
||||
const db = await dbPromise
|
||||
return await new Promise((resolve, reject) => {
|
||||
const tx = db.transaction(STORE, 'readwrite')
|
||||
const store = tx.objectStore(STORE)
|
||||
for (let status of statuses) {
|
||||
store.put(transformStatusForStorage(status))
|
||||
}
|
||||
tx.oncomplete = resolve
|
||||
tx.onerror = reject
|
||||
})
|
||||
}
|
|
@ -32,23 +32,4 @@ export function getAccessTokenFromAuthCode(instanceName, clientId, clientSecret,
|
|||
grant_type: 'authorization_code',
|
||||
code: code
|
||||
})
|
||||
}
|
||||
|
||||
export function getHomeTimeline(instanceName, accessToken, since, limit) {
|
||||
let url = `https://${instanceName}/api/v1/timelines/home`
|
||||
|
||||
let params = {}
|
||||
if (since) {
|
||||
params[since] = since
|
||||
}
|
||||
|
||||
if (limit) {
|
||||
params[limit] = limit
|
||||
}
|
||||
|
||||
url += '?' + paramsString(params)
|
||||
|
||||
return get(url, {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
})
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { get, paramsString } from '../ajax'
|
||||
|
||||
export function getHomeTimeline(instanceName, accessToken, maxId, since) {
|
||||
let url = `https://${instanceName}/api/v1/timelines/home`
|
||||
|
||||
let params = {}
|
||||
if (since) {
|
||||
params.since = since
|
||||
}
|
||||
|
||||
if (maxId) {
|
||||
params.max_id = maxId
|
||||
}
|
||||
|
||||
url += '?' + paramsString(params)
|
||||
|
||||
return get(url, {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
})
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Merge two lists of statuses for the same timeline, e.g. one from IDB
|
||||
// and another from the network. In case of duplicates, prefer the fresh.
|
||||
export function mergeStatuses(leftStatuses, rightStatuses) {
|
||||
let leftIndex = 0
|
||||
let rightIndex = 0
|
||||
let merged = []
|
||||
while (leftIndex < leftStatuses.length || rightIndex < rightStatuses.length) {
|
||||
if (leftIndex === leftStatuses.length) {
|
||||
merged.push(rightStatuses[rightIndex])
|
||||
rightIndex++
|
||||
continue
|
||||
}
|
||||
if (rightIndex === rightStatuses.length) {
|
||||
merged.push(leftStatuses[leftIndex])
|
||||
leftIndex++
|
||||
continue
|
||||
}
|
||||
let left = leftStatuses[leftIndex]
|
||||
let right = rightStatuses[rightIndex]
|
||||
if (right.id === left.id) {
|
||||
merged.push(right.pinafore_stale ? left : right)
|
||||
rightIndex++
|
||||
leftIndex++
|
||||
} else if (parseInt(right.id, 10) > parseInt(left.id, 10)) {
|
||||
merged.push(right)
|
||||
rightIndex++
|
||||
} else {
|
||||
merged.push(left)
|
||||
leftIndex++
|
||||
}
|
||||
}
|
||||
return merged
|
||||
}
|
Loading…
Reference in New Issue