server side storage support for collections + fixes
This commit is contained in:
parent
8a67fe93c2
commit
72e238ceb3
|
@ -1,5 +1,5 @@
|
||||||
import { toRaw } from 'vue'
|
import { toRaw } from 'vue'
|
||||||
import { isEqual, uniqBy, cloneDeep, set } from 'lodash'
|
import { isEqual, uniqWith, cloneDeep, set, get, clamp } from 'lodash'
|
||||||
import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js'
|
import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js'
|
||||||
|
|
||||||
export const VERSION = 1
|
export const VERSION = 1
|
||||||
|
@ -36,6 +36,17 @@ export const newUserFlags = {
|
||||||
updateCounter: CURRENT_UPDATE_COUNTER // new users don't need to see update notification
|
updateCounter: CURRENT_UPDATE_COUNTER // new users don't need to see update notification
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const _moveItemInArray = (array, value, movement) => {
|
||||||
|
const oldIndex = array.indexOf(value)
|
||||||
|
const newIndex = oldIndex + movement
|
||||||
|
const newArray = [...array]
|
||||||
|
// remove old
|
||||||
|
newArray.splice(oldIndex, 1)
|
||||||
|
// add new
|
||||||
|
newArray.splice(clamp(newIndex, 0, newArray.length + 1), 0, value)
|
||||||
|
return newArray
|
||||||
|
}
|
||||||
|
|
||||||
const _wrapData = (data) => ({
|
const _wrapData = (data) => ({
|
||||||
...data,
|
...data,
|
||||||
_timestamp: Date.now(),
|
_timestamp: Date.now(),
|
||||||
|
@ -98,6 +109,23 @@ export const _mergeFlags = (recent, stale, allFlagKeys) => {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _mergeJournal = (a, b) => uniqWith(
|
||||||
|
[
|
||||||
|
...(Array.isArray(a) ? a : []),
|
||||||
|
...(Array.isArray(b) ? b : [])
|
||||||
|
].sort((a, b) => a.timestamp > b.timestamp ? -1 : 1),
|
||||||
|
(a, b) => {
|
||||||
|
if (a.operation !== b.operation) return false
|
||||||
|
switch (a.operation) {
|
||||||
|
case 'set':
|
||||||
|
case 'arrangeSet':
|
||||||
|
return a.path === b.path
|
||||||
|
default:
|
||||||
|
return a.path === b.path && a.timestamp === b.timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).reverse()
|
||||||
|
|
||||||
export const _mergePrefs = (recent, stale, allFlagKeys) => {
|
export const _mergePrefs = (recent, stale, allFlagKeys) => {
|
||||||
if (!stale) return recent
|
if (!stale) return recent
|
||||||
if (!recent) return stale
|
if (!recent) return stale
|
||||||
|
@ -114,13 +142,7 @@ export const _mergePrefs = (recent, stale, allFlagKeys) => {
|
||||||
* shouldn't be used with collections!
|
* shouldn't be used with collections!
|
||||||
*/
|
*/
|
||||||
const resultOutput = { ...recentData }
|
const resultOutput = { ...recentData }
|
||||||
const totalJournal = uniqBy(
|
const totalJournal = _mergeJournal(staleJournal, recentJournal)
|
||||||
[
|
|
||||||
...(Array.isArray(recentJournal) ? recentJournal : []),
|
|
||||||
...(Array.isArray(staleJournal) ? staleJournal : [])
|
|
||||||
].sort((a, b) => a.timestamp > b.timestamp ? -1 : 1),
|
|
||||||
'path'
|
|
||||||
).reverse()
|
|
||||||
totalJournal.forEach(({ path, timestamp, operation, args }) => {
|
totalJournal.forEach(({ path, timestamp, operation, args }) => {
|
||||||
if (path.startsWith('_')) {
|
if (path.startsWith('_')) {
|
||||||
console.error(`journal contains entry to edit internal (starts with _) field '${path}', something is incorrect here, ignoring.`)
|
console.error(`journal contains entry to edit internal (starts with _) field '${path}', something is incorrect here, ignoring.`)
|
||||||
|
@ -130,6 +152,17 @@ export const _mergePrefs = (recent, stale, allFlagKeys) => {
|
||||||
case 'set':
|
case 'set':
|
||||||
set(resultOutput, path, args[0])
|
set(resultOutput, path, args[0])
|
||||||
break
|
break
|
||||||
|
case 'addToCollection':
|
||||||
|
set(resultOutput, path, Array.from(new Set(get(resultOutput, path)).add(args[0])))
|
||||||
|
break
|
||||||
|
case 'removeFromCollection':
|
||||||
|
set(resultOutput, path, Array.from(new Set(get(resultOutput, path)).remove(args[0])))
|
||||||
|
break
|
||||||
|
case 'reorderCollection': {
|
||||||
|
const [value, movement] = args
|
||||||
|
set(resultOutput, path, _moveItemInArray(get(resultOutput, path), value, movement))
|
||||||
|
break
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
console.error(`Unknown journal operation: '${operation}', did we forget to run reverse migrations beforehand?`)
|
console.error(`Unknown journal operation: '${operation}', did we forget to run reverse migrations beforehand?`)
|
||||||
}
|
}
|
||||||
|
@ -260,13 +293,56 @@ export const mutations = {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
set(state.prefsStorage, path, value)
|
set(state.prefsStorage, path, value)
|
||||||
state.prefsStorage._journal = uniqBy(
|
state.prefsStorage._journal = [
|
||||||
[
|
|
||||||
...state.prefsStorage._journal,
|
...state.prefsStorage._journal,
|
||||||
{ command: 'set', path, args: [value], timestamp: Date.now() }
|
{ command: 'set', path, args: [value], timestamp: Date.now() }
|
||||||
].sort((a, b) => a.timestamp > b.timestamp ? -1 : 1),
|
]
|
||||||
'path'
|
},
|
||||||
).reverse()
|
addCollectionPreference (state, { path, value }) {
|
||||||
|
if (path.startsWith('_')) {
|
||||||
|
console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const collection = new Set(get(state.prefsStorage, path))
|
||||||
|
collection.add(value)
|
||||||
|
set(state.prefsStorage, path, collection)
|
||||||
|
state.prefsStorage._journal = [
|
||||||
|
...state.prefsStorage._journal,
|
||||||
|
{ command: 'addToCollection', path, args: [value], timestamp: Date.now() }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
removeCollectionPreference (state, { path, value }) {
|
||||||
|
if (path.startsWith('_')) {
|
||||||
|
console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const collection = new Set(get(state.prefsStorage, path))
|
||||||
|
collection.remove(value)
|
||||||
|
set(state.prefsStorage, path, collection)
|
||||||
|
state.prefsStorage._journal = [
|
||||||
|
...state.prefsStorage._journal,
|
||||||
|
{ command: 'removeFromCollection', path, args: [value], timestamp: Date.now() }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reorderCollectionPreference (state, { path, value, movement }) {
|
||||||
|
if (path.startsWith('_')) {
|
||||||
|
console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const collection = get(state.prefsStorage, path)
|
||||||
|
const newCollection = _moveItemInArray(collection, value, movement)
|
||||||
|
set(state.prefsStorage, path, newCollection)
|
||||||
|
state.prefsStorage._journal = [
|
||||||
|
...state.prefsStorage._journal,
|
||||||
|
{ command: 'arrangeCollection', path, args: [value], timestamp: Date.now() }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
updateCache (state) {
|
||||||
|
state.prefsStorage._journal = _mergeJournal(state.prefsStorage._journal)
|
||||||
|
state.cache = _wrapData({
|
||||||
|
flagStorage: toRaw(state.flagStorage),
|
||||||
|
prefsStorage: toRaw(state.prefsStorage)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,10 +355,7 @@ const serverSideStorage = {
|
||||||
pushServerSideStorage ({ state, rootState, commit }, { force = false } = {}) {
|
pushServerSideStorage ({ state, rootState, commit }, { force = false } = {}) {
|
||||||
const needPush = state.dirty || force
|
const needPush = state.dirty || force
|
||||||
if (!needPush) return
|
if (!needPush) return
|
||||||
state.cache = _wrapData({
|
commit('updateCache')
|
||||||
flagStorage: toRaw(state.flagStorage),
|
|
||||||
prefsStorage: toRaw(state.prefsStorage)
|
|
||||||
})
|
|
||||||
const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } }
|
const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } }
|
||||||
rootState.api.backendInteractor
|
rootState.api.backendInteractor
|
||||||
.updateProfile({ params })
|
.updateProfile({ params })
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
VERSION,
|
VERSION,
|
||||||
COMMAND_TRIM_FLAGS,
|
COMMAND_TRIM_FLAGS,
|
||||||
COMMAND_TRIM_FLAGS_AND_RESET,
|
COMMAND_TRIM_FLAGS_AND_RESET,
|
||||||
|
_moveItemInArray,
|
||||||
_getRecentData,
|
_getRecentData,
|
||||||
_getAllFlags,
|
_getAllFlags,
|
||||||
_mergeFlags,
|
_mergeFlags,
|
||||||
|
@ -62,7 +63,7 @@ describe('The serverSideStorage module', () => {
|
||||||
updateCounter: 1
|
updateCounter: 1
|
||||||
},
|
},
|
||||||
prefsStorage: {
|
prefsStorage: {
|
||||||
...defaultState.flagStorage,
|
...defaultState.prefsStorage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +107,7 @@ describe('The serverSideStorage module', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('setPreference', () => {
|
describe('setPreference', () => {
|
||||||
const { setPreference } = mutations
|
const { setPreference, updateCache } = mutations
|
||||||
|
|
||||||
it('should set preference and update journal log accordingly', () => {
|
it('should set preference and update journal log accordingly', () => {
|
||||||
const state = cloneDeep(defaultState)
|
const state = cloneDeep(defaultState)
|
||||||
|
@ -122,11 +123,12 @@ describe('The serverSideStorage module', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should keep journal to a minimum (one entry per path)', () => {
|
it('should keep journal to a minimum (one entry per path for sets)', () => {
|
||||||
const state = cloneDeep(defaultState)
|
const state = cloneDeep(defaultState)
|
||||||
setPreference(state, { path: 'simple.testing', value: 1 })
|
setPreference(state, { path: 'simple.testing', value: 1 })
|
||||||
setPreference(state, { path: 'simple.testing', value: 2 })
|
setPreference(state, { path: 'simple.testing', value: 2 })
|
||||||
expect(state.prefsStorage.simple.testing).to.eql(1)
|
updateCache(state)
|
||||||
|
expect(state.prefsStorage.simple.testing).to.eql(2)
|
||||||
expect(state.prefsStorage._journal.length).to.eql(1)
|
expect(state.prefsStorage._journal.length).to.eql(1)
|
||||||
expect(state.prefsStorage._journal[0]).to.eql({
|
expect(state.prefsStorage._journal[0]).to.eql({
|
||||||
path: 'simple.testing',
|
path: 'simple.testing',
|
||||||
|
@ -140,6 +142,16 @@ describe('The serverSideStorage module', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('helper functions', () => {
|
describe('helper functions', () => {
|
||||||
|
describe('_moveItemInArray', () => {
|
||||||
|
it('should move item according to movement value', () => {
|
||||||
|
expect(_moveItemInArray([1, 2, 3, 4], 4, -1)).to.eql([1, 2, 4, 3])
|
||||||
|
expect(_moveItemInArray([1, 2, 3, 4], 1, 2)).to.eql([2, 3, 1, 4])
|
||||||
|
})
|
||||||
|
it('should clamp movement to within array', () => {
|
||||||
|
expect(_moveItemInArray([1, 2, 3, 4], 4, -10)).to.eql([4, 1, 2, 3])
|
||||||
|
expect(_moveItemInArray([1, 2, 3, 4], 3, 99)).to.eql([1, 2, 4, 3])
|
||||||
|
})
|
||||||
|
})
|
||||||
describe('_getRecentData', () => {
|
describe('_getRecentData', () => {
|
||||||
it('should handle nulls correctly', () => {
|
it('should handle nulls correctly', () => {
|
||||||
expect(_getRecentData(null, null)).to.eql({ recent: null, stale: null, needUpload: true })
|
expect(_getRecentData(null, null)).to.eql({ recent: null, stale: null, needUpload: true })
|
||||||
|
|
Loading…
Reference in New Issue