refactor store

This commit is contained in:
Nolan Lawson 2018-01-28 13:09:39 -08:00
parent 88d59678f2
commit 90af9eedde
25 changed files with 181 additions and 182 deletions

View file

@ -1,4 +1,4 @@
import { store } from '../_utils/store' import { store } from '../_store/store'
import { database } from '../_utils/database/database' import { database } from '../_utils/database/database'
import { getTimeline } from '../_utils/mastodon/timelines' import { getTimeline } from '../_utils/mastodon/timelines'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'

View file

@ -17,7 +17,7 @@
<script> <script>
import Nav from './Nav.html'; import Nav from './Nav.html';
import VirtualListContainer from './virtualList/VirtualListContainer.html' import VirtualListContainer from './virtualList/VirtualListContainer.html'
import { store } from '../_utils/store' import { store } from '../_store/store'
export default { export default {
oncreate() { oncreate() {

View file

@ -297,7 +297,7 @@
import { mark, stop } from '../../_utils/marks' import { mark, stop } from '../../_utils/marks'
import IntlRelativeFormat from 'intl-relativeformat' import IntlRelativeFormat from 'intl-relativeformat'
import { replaceAll } from '../../_utils/replaceAll' import { replaceAll } from '../../_utils/replaceAll'
import { store } from '../../_utils/store' import { store } from '../../_store/store'
const relativeFormat = new IntlRelativeFormat('en-US'); const relativeFormat = new IntlRelativeFormat('en-US');

View file

@ -35,7 +35,7 @@
import LoadingSpinner from '../LoadingSpinner.html' import LoadingSpinner from '../LoadingSpinner.html'
// TODO: the transition seems to occasionally cause an error in Svelte, transition_run is undefined // TODO: the transition seems to occasionally cause an error in Svelte, transition_run is undefined
//import { fade } from 'svelte-transitions' //import { fade } from 'svelte-transitions'
import { store } from '../../_utils/store' import { store } from '../../_store/store'
export default { export default {
oncreate() { oncreate() {

View file

@ -16,7 +16,7 @@
} }
</style> </style>
<script> <script>
import { store } from '../../_utils/store' import { store } from '../../_store/store'
import StatusListItem from './StatusListItem.html' import StatusListItem from './StatusListItem.html'
import LoadingFooter from './LoadingFooter.html' import LoadingFooter from './LoadingFooter.html'
import VirtualList from '../virtualList/VirtualList.html' import VirtualList from '../virtualList/VirtualList.html'

View file

@ -0,0 +1,40 @@
import { Store } from 'svelte/store'
const LS = process.browser && localStorage
export class LocalStorageStore extends Store {
constructor(state, keysToWatch) {
super(state)
if (!process.browser) {
return
}
this._keysToWatch = keysToWatch
this._keysToSave = {}
let newState = {}
for (let i = 0, len = LS.length; i < len; i++) {
let key = LS.key(i)
if (key.startsWith('store_')) {
let item = LS.getItem(key)
newState[key.substring(6)] = item === 'undefined' ? undefined : JSON.parse(item)
}
}
this.set(newState)
this.onchange((state, changed) => {
Object.keys(changed).forEach(change => {
if (this._keysToWatch.has(change)) {
this._keysToSave[change] = true
}
})
})
}
save() {
if (!process.browser) {
return
}
Object.keys(this._keysToSave).forEach(key => {
LS.setItem(`store_${key}`, JSON.stringify(this.get(key)))
})
this._keysToSave = {}
}
}

View file

@ -0,0 +1,7 @@
import { instanceComputations } from './instanceComputations'
import { timelineComputations } from './timelineComputations'
export function computations(store) {
instanceComputations(store)
timelineComputations(store)
}

View file

@ -0,0 +1,49 @@
export function instanceComputations(store) {
store.compute(
'isUserLoggedIn',
['currentInstance', 'loggedInInstances'],
(currentInstance, loggedInInstances) => !!(currentInstance && Object.keys(loggedInInstances).includes(currentInstance))
)
store.compute(
'loggedInInstancesAsList',
['currentInstance', 'loggedInInstances', 'loggedInInstancesInOrder'],
(currentInstance, loggedInInstances, loggedInInstancesInOrder) => {
return loggedInInstancesInOrder.map(instanceName => {
return Object.assign({
current: currentInstance === instanceName,
name: instanceName
}, loggedInInstances[instanceName])
})
}
)
store.compute(
'currentInstanceData',
['currentInstance', 'loggedInInstances'],
(currentInstance, loggedInInstances) => {
return Object.assign({
name: currentInstance
}, loggedInInstances[currentInstance])
})
store.compute(
'accessToken',
['currentInstanceData'],
(currentInstanceData) => currentInstanceData.access_token
)
store.compute(
'currentTheme',
['currentInstance', 'instanceThemes'],
(currentInstance, instanceThemes) => {
return instanceThemes[currentInstance] || 'default'
}
)
store.compute(
'currentVerifyCredentials',
['currentInstance', 'verifyCredentials'],
(currentInstance, verifyCredentials) => verifyCredentials && verifyCredentials[currentInstance]
)
}

19
routes/_store/mixins.js Normal file
View file

@ -0,0 +1,19 @@
function timelineMixins(Store) {
Store.prototype.setForTimeline = function (instanceName, timelineName, obj) {
let timelines = this.get('timelines') || {}
let timelineData = timelines[instanceName] || {}
timelineData[timelineName] = Object.assign(timelineData[timelineName] || {}, obj)
timelines[instanceName] = timelineData
this.set({timelines: timelines})
}
Store.prototype.getForTimeline = function (instanceName, timelineName, key) {
let timelines = this.get('timelines') || {}
let timelineData = timelines[instanceName] || {}
return (timelineData[timelineName] || {})[key]
}
}
export function mixins(Store) {
timelineMixins(Store)
}

View file

@ -1,6 +1,6 @@
import { updateVerifyCredentialsForInstance } from '../settings/instances/_actions/[instanceName]' import { updateVerifyCredentialsForInstance } from '../settings/instances/_actions/[instanceName]'
export function storeObservers(store) { export function observers(store) {
store.observe('currentInstance', (currentInstance) => { store.observe('currentInstance', (currentInstance) => {
updateVerifyCredentialsForInstance(currentInstance) updateVerifyCredentialsForInstance(currentInstance)
}) })

38
routes/_store/store.js Normal file
View file

@ -0,0 +1,38 @@
import { observers } from './obsevers'
import { computations } from './computations'
import { mixins } from './mixins'
import { LocalStorageStore } from './LocalStorageStore'
const KEYS_TO_STORE_IN_LOCAL_STORAGE = new Set([
"currentInstance",
"currentRegisteredInstance",
"currentRegisteredInstanceName",
"instanceNameInSearch",
"instanceThemes",
"loggedInInstances",
"loggedInInstancesInOrder"
])
class PinaforeStore extends LocalStorageStore {
constructor(state) {
super(state, KEYS_TO_STORE_IN_LOCAL_STORAGE)
}
}
const store = new PinaforeStore({
instanceNameInSearch: '',
currentInstance: null,
loggedInInstances: {},
loggedInInstancesInOrder: [],
instanceThemes: {}
})
mixins(PinaforeStore)
computations(store)
observers(store)
if (process.browser && process.env.NODE_ENV !== 'production') {
window.store = store // for debugging
}
export { store }

View file

@ -0,0 +1,11 @@
export function timelineComputations(store) {
store.compute('currentTimelineData', ['currentInstance', 'currentTimeline', 'timelines'],
(currentInstance, currentTimeline, timelines) => {
return ((timelines && timelines[currentInstance]) || {})[currentTimeline] || {}
})
store.compute('statusIds', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.statusIds || [])
store.compute('runningUpdate', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.runningUpdate)
store.compute('initialized', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.initialized)
store.compute('lastStatusId', ['statusIds'], (statusIds) => statusIds.length && statusIds[statusIds.length - 1])
}

View file

@ -1,27 +0,0 @@
// from blob-util
function blobToBinaryString(blob) {
return new Promise(function (resolve, reject) {
var reader = new FileReader();
var hasBinaryString = typeof reader.readAsBinaryString === 'function';
reader.onloadend = function (e) {
var result = e.target.result || '';
if (hasBinaryString) {
return resolve(result);
}
resolve(arrayBufferToBinaryString(result));
};
reader.onerror = reject;
if (hasBinaryString) {
reader.readAsBinaryString(blob);
} else {
reader.readAsArrayBuffer(blob);
}
});
}
export function blobToBase64(blob) {
return blobToBinaryString(blob).then(function (binary) {
// web-safe variant
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
});
}

View file

@ -1,138 +0,0 @@
import { Store } from 'svelte/store.js'
import { storeObservers } from './storeObservers'
const LOCAL_STORAGE_KEYS = new Set([
"currentInstance",
"currentRegisteredInstance",
"currentRegisteredInstanceName",
"instanceNameInSearch",
"instanceThemes",
"loggedInInstances",
"loggedInInstancesInOrder"
])
const LS = process.browser && localStorage
class PinaforeStore extends Store {
constructor(state) {
super(state)
if (process.browser) {
this.keysToStore = {}
let newState = {}
for (let i = 0, len = LS.length; i < len; i++) {
let key = LS.key(i)
if (key.startsWith('store_')) {
let item = LS.getItem(key)
newState[key.substring(6)] = item === 'undefined' ? undefined : JSON.parse(item)
}
}
this.set(newState)
this.onchange((state, changed) => {
Object.keys(changed).forEach(change => {
if (LOCAL_STORAGE_KEYS.has(change)) {
this.keysToStore[change] = true
}
})
})
}
}
save() {
if (process.browser) {
Object.keys(this.keysToStore).forEach(key => {
LS.setItem(`store_${key}`, JSON.stringify(this.get(key)))
})
this.keysToStore = {}
}
}
setForTimeline(instanceName, timelineName, obj) {
let timelines = this.get('timelines') || {}
let timelineData = timelines[instanceName] || {}
timelineData[timelineName] = Object.assign(timelineData[timelineName] || {}, obj)
timelines[instanceName] = timelineData
this.set({timelines: timelines})
}
getForTimeline(instanceName, timelineName, key) {
let timelines = this.get('timelines') || {}
let timelineData = timelines[instanceName] || {}
return (timelineData[timelineName] || {})[key]
}
}
const store = new PinaforeStore({
instanceNameInSearch: '',
currentRegisteredInstance: null,
currentRegisteredInstanceName: '',
currentInstance: null,
loggedInInstances: {},
loggedInInstancesInOrder: [],
instanceThemes: {}
})
store.compute(
'isUserLoggedIn',
['currentInstance', 'loggedInInstances'],
(currentInstance, loggedInInstances) => !!(currentInstance && Object.keys(loggedInInstances).includes(currentInstance))
)
store.compute(
'loggedInInstancesAsList',
['currentInstance', 'loggedInInstances', 'loggedInInstancesInOrder'],
(currentInstance, loggedInInstances, loggedInInstancesInOrder) => {
return loggedInInstancesInOrder.map(instanceName => {
return Object.assign({
current: currentInstance === instanceName,
name: instanceName
}, loggedInInstances[instanceName])
})
}
)
store.compute(
'currentInstanceData',
['currentInstance', 'loggedInInstances'],
(currentInstance, loggedInInstances) => {
return Object.assign({
name: currentInstance
}, loggedInInstances[currentInstance])
})
store.compute(
'accessToken',
['currentInstanceData'],
(currentInstanceData) => currentInstanceData.access_token
)
store.compute(
'currentTheme',
['currentInstance', 'instanceThemes'],
(currentInstance, instanceThemes) => {
return instanceThemes[currentInstance] || 'default'
}
)
store.compute(
'currentVerifyCredentials',
['currentInstance', 'verifyCredentials'],
(currentInstance, verifyCredentials) => verifyCredentials && verifyCredentials[currentInstance]
)
store.compute('currentTimelineData', ['currentInstance', 'currentTimeline', 'timelines'],
(currentInstance, currentTimeline, timelines) => {
return ((timelines && timelines[currentInstance]) || {})[currentTimeline] || {}
})
store.compute('statusIds', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.statusIds || [])
store.compute('runningUpdate', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.runningUpdate)
store.compute('initialized', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.initialized)
store.compute('lastStatusId', ['statusIds'], (statusIds) => statusIds.length && statusIds[statusIds.length - 1])
storeObservers(store)
if (process.browser && process.env.NODE_ENV !== 'production') {
window.store = store // for debugging
}
export { store }

View file

@ -32,7 +32,7 @@
import Layout from '../_components/Layout.html' import Layout from '../_components/Layout.html'
import LazyTimeline from '../_components/timeline/LazyTimeline.html' import LazyTimeline from '../_components/timeline/LazyTimeline.html'
import FreeTextLayout from '../_components/FreeTextLayout.html' import FreeTextLayout from '../_components/FreeTextLayout.html'
import { store } from '../_utils/store.js' import { store } from '../_store/store.js'
import HiddenFromSSR from '../_components/HiddenFromSSR' import HiddenFromSSR from '../_components/HiddenFromSSR'
import DynamicPageBanner from '../_components/DynamicPageBanner.html' import DynamicPageBanner from '../_components/DynamicPageBanner.html'
import { updateProfileAndRelationship } from './_actions/[accountId]' import { updateProfileAndRelationship } from './_actions/[accountId]'

View file

@ -1,6 +1,6 @@
import { getAccount, getRelationship } from '../../_utils/mastodon/user' import { getAccount, getRelationship } from '../../_utils/mastodon/user'
import { database } from '../../_utils/database/database' import { database } from '../../_utils/database/database'
import { store } from '../../_utils/store' import { store } from '../../_store/store'
async function updateAccount(accountId, instanceName, accessToken) { async function updateAccount(accountId, instanceName, accessToken) {
let localPromise = database.getAccount(instanceName, accountId) let localPromise = database.getAccount(instanceName, accountId)

View file

@ -20,7 +20,7 @@
import Layout from './_components/Layout.html' import Layout from './_components/Layout.html'
import LazyTimeline from './_components/timeline/LazyTimeline.html' import LazyTimeline from './_components/timeline/LazyTimeline.html'
import FreeTextLayout from './_components/FreeTextLayout.html' import FreeTextLayout from './_components/FreeTextLayout.html'
import { store } from './_utils/store.js' import { store } from './_store/store.js'
import HiddenFromSSR from './_components/HiddenFromSSR' import HiddenFromSSR from './_components/HiddenFromSSR'
export default { export default {

View file

@ -15,7 +15,7 @@
import Layout from './_components/Layout.html' import Layout from './_components/Layout.html'
import NotLoggedInHome from './_components/NotLoggedInHome.html' import NotLoggedInHome from './_components/NotLoggedInHome.html'
import LazyTimeline from './_components/timeline/LazyTimeline.html' import LazyTimeline from './_components/timeline/LazyTimeline.html'
import { store } from './_utils/store.js' import { store } from './_store/store.js'
export default { export default {
store: () => store, store: () => store,

View file

@ -20,7 +20,7 @@
import Layout from './_components/Layout.html' import Layout from './_components/Layout.html'
import LazyTimeline from './_components/timeline/LazyTimeline.html' import LazyTimeline from './_components/timeline/LazyTimeline.html'
import FreeTextLayout from './_components/FreeTextLayout.html' import FreeTextLayout from './_components/FreeTextLayout.html'
import { store } from './_utils/store.js' import { store } from './_store/store.js'
import HiddenFromSSR from './_components/HiddenFromSSR' import HiddenFromSSR from './_components/HiddenFromSSR'
export default { export default {

View file

@ -94,7 +94,7 @@
} }
</style> </style>
<script> <script>
import { store } from '../../_utils/store' import { store } from '../../_store/store'
import Layout from '../../_components/Layout.html' import Layout from '../../_components/Layout.html'
import SettingsLayout from '../_components/SettingsLayout.html' import SettingsLayout from '../_components/SettingsLayout.html'
import { import {

View file

@ -1,5 +1,5 @@
import { getVerifyCredentials } from '../../../_utils/mastodon/user' import { getVerifyCredentials } from '../../../_utils/mastodon/user'
import { store } from '../../../_utils/store' import { store } from '../../../_store/store'
import { switchToTheme } from '../../../_utils/themeEngine' import { switchToTheme } from '../../../_utils/themeEngine'
import { toast } from '../../../_utils/toast' import { toast } from '../../../_utils/toast'
import { database } from '../../../_utils/database/database' import { database } from '../../../_utils/database/database'

View file

@ -3,7 +3,7 @@ import { getInstanceInfo } from '../../../_utils/mastodon/instance'
import { goto } from 'sapper/runtime.js' import { goto } from 'sapper/runtime.js'
import { switchToTheme } from '../../../_utils/themeEngine' import { switchToTheme } from '../../../_utils/themeEngine'
import { database } from '../../../_utils/database/database' import { database } from '../../../_utils/database/database'
import { store } from '../../../_utils/store' import { store } from '../../../_store/store'
import { updateVerifyCredentialsForInstance } from './[instanceName]' import { updateVerifyCredentialsForInstance } from './[instanceName]'
const REDIRECT_URI = (typeof location !== 'undefined' ? const REDIRECT_URI = (typeof location !== 'undefined' ?

View file

@ -70,7 +70,7 @@
<script> <script>
import Layout from '../../_components/Layout.html'; import Layout from '../../_components/Layout.html';
import SettingsLayout from '../_components/SettingsLayout.html' import SettingsLayout from '../_components/SettingsLayout.html'
import { store } from '../../_utils/store' import { store } from '../../_store/store'
import LoadingMask from '../../_components/LoadingMask' import LoadingMask from '../../_components/LoadingMask'
import { logInToInstance, handleOauthCode } from './_actions/add' import { logInToInstance, handleOauthCode } from './_actions/add'

View file

@ -25,7 +25,7 @@
</SettingsLayout> </SettingsLayout>
</Layout> </Layout>
<script> <script>
import { store } from '../../_utils/store' import { store } from '../../_store/store'
import Layout from '../../_components/Layout.html' import Layout from '../../_components/Layout.html'
import SettingsLayout from '../_components/SettingsLayout.html' import SettingsLayout from '../_components/SettingsLayout.html'
import SettingsList from '../_components/SettingsList.html' import SettingsList from '../_components/SettingsList.html'

View file

@ -26,7 +26,7 @@
import Layout from '../_components/Layout.html' import Layout from '../_components/Layout.html'
import LazyTimeline from '../_components/timeline/LazyTimeline.html' import LazyTimeline from '../_components/timeline/LazyTimeline.html'
import FreeTextLayout from '../_components/FreeTextLayout.html' import FreeTextLayout from '../_components/FreeTextLayout.html'
import { store } from '../_utils/store.js' import { store } from '../_store/store.js'
import HiddenFromSSR from '../_components/HiddenFromSSR' import HiddenFromSSR from '../_components/HiddenFromSSR'
import DynamicPageBanner from '../_components/DynamicPageBanner.html' import DynamicPageBanner from '../_components/DynamicPageBanner.html'