migrate to pinia

This commit is contained in:
Henry Jameson 2025-03-23 20:03:25 +02:00
parent eaf7efdcff
commit f347897b29
10 changed files with 330 additions and 317 deletions

View file

@ -1,14 +1,19 @@
import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import GestureService from '../../services/gesture_service/gesture_service'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import {
unseenNotificationsFromStore,
countExtraNotifications
} from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import { mapGetters } from 'vuex'
import { mapState } from 'pinia'
import { useAnnouncementsStore } from 'src/stores/announcements'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
@ -18,7 +23,6 @@ import {
faMinus,
faCheckDouble
} from '@fortawesome/free-solid-svg-icons'
import { useAnnouncementsStore } from 'src/stores/announcements'
library.add(
faTimes,
@ -71,10 +75,9 @@ const MobileNav = {
return this.$route.name === 'chat'
},
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
...mapGetters(['unreadChatCount']),
chatsPinned () {
return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats')
},
...mapState(useServerSideStorageStore, {
pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems).has('chats')
}),
shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout
},

View file

@ -7,7 +7,9 @@ import { filterNavigation } from 'src/components/navigation/filter.js'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import { useAnnouncementsStore } from 'src/stores/announcements'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -76,19 +78,19 @@ const NavPanel = {
this.editMode = !this.editMode
},
toggleCollapse () {
this.$store.commit('setPreference', { path: 'simple.collapseNav', value: !this.collapsed })
this.$store.dispatch('pushServerSideStorage')
useServerSideStorageStore().setPreference({ path: 'simple.collapseNav', value: !this.collapsed })
useServerSideStorageStore().pushServerSideStorage()
},
isPinned (item) {
return this.pinnedItems.has(item)
},
togglePin (item) {
if (this.isPinned(item)) {
this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value: item })
useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value: item })
} else {
this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value: item })
useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value: item })
}
this.$store.dispatch('pushServerSideStorage')
useServerSideStorageStore().pushServerSideStorage()
}
},
computed: {
@ -96,14 +98,16 @@ const NavPanel = {
unreadAnnouncementCount: 'unreadAnnouncementCount',
supportsAnnouncements: store => store.supportsAnnouncements
}),
...mapPiniaState(useServerSideStorageStore, {
collapsed: store => store.prefsStorage.simple.collapseNav,
pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems)
}),
...mapState({
currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems),
collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav,
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
}),
timelinesItems () {

View file

@ -3,8 +3,10 @@ import { routeTo } from 'src/components/navigation/navigation.js'
import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
import { mapStores } from 'pinia'
import { mapStores, mapState as mapPiniaState } from 'pinia'
import { useAnnouncementsStore } from 'src/stores/announcements'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
library.add(faThumbtack)
@ -19,11 +21,11 @@ const NavigationEntry = {
},
togglePin (value) {
if (this.isPinned(value)) {
this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value })
useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value })
} else {
this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value })
useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value })
}
this.$store.dispatch('pushServerSideStorage')
useServerSideStorageStore().pushServerSideStorage()
}
},
computed: {
@ -35,9 +37,11 @@ const NavigationEntry = {
},
...mapStores(useAnnouncementsStore),
...mapState({
currentUser: state => state.users.currentUser,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
})
currentUser: state => state.users.currentUser
}),
...mapPiniaState(useServerSideStorageStore, {
pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems)
}),
}
}

View file

@ -20,6 +20,7 @@ import {
import { useListsStore } from 'src/stores/lists'
import { useAnnouncementsStore } from 'src/stores/announcements'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
library.add(
faUsers,
@ -54,15 +55,17 @@ const NavPanel = {
supportsAnnouncements: store => store.supportsAnnouncements
}),
...mapPiniaState(useBookmarkFoldersStore, {
bookmarks: getBookmarkFolderEntries
bookmarks: getBookmarkFolderEntries,
}),
...mapPiniaState(useServerSideStorageStore, {
pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems)
}),
...mapState({
currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
}),
pinnedList () {
if (!this.currentUser) {

View file

@ -1,10 +1,12 @@
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
import ActionButtonContainer from './action_button_container.vue'
import Popover from 'src/components/popover/popover.vue'
import genRandomSeed from 'src/services/random_seed/random_seed.service.js'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { BUTTONS } from './buttons_definitions.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@ -36,8 +38,8 @@ const StatusActionButtons = {
ActionButtonContainer
},
computed: {
...mapState({
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedStatusActions)
...mapState(useServerSideStorageStore, {
pinnedItems: store => new Set(store.prefsStorage.collections.pinnedStatusActions)
}),
buttons () {
return BUTTONS.filter(x => x.if ? x.if(this.funcArg) : true)
@ -101,12 +103,12 @@ const StatusActionButtons = {
return this.pinnedItems.has(button.name)
},
unpin (button) {
this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedStatusActions', value: button.name })
this.$store.dispatch('pushServerSideStorage')
useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedStatusActions', value: button.name })
useServerSideStorageStore().pushServerSideStorage()
},
pin (button) {
this.$store.commit('addCollectionPreference', { path: 'collections.pinnedStatusActions', value: button.name })
this.$store.dispatch('pushServerSideStorage')
useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedStatusActions', value: button.name })
useServerSideStorageStore().pushServerSideStorage()
},
getComponent (button) {
if (!this.$store.state.users.currentUser && button.anonLink) {

View file

@ -3,6 +3,8 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import pleromaTanMask from 'src/assets/pleromatan_apology_mask.png'
import pleromaTanFoxMask from 'src/assets/pleromatan_apology_fox_mask.png'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import {
faTimes
} from '@fortawesome/free-solid-svg-icons'
@ -36,8 +38,8 @@ const UpdateNotification = {
shouldShow () {
return !this.$store.state.instance.disableUpdateNotification &&
this.$store.state.users.currentUser &&
this.$store.state.serverSideStorage.flagStorage.updateCounter < CURRENT_UPDATE_COUNTER &&
!this.$store.state.serverSideStorage.prefsStorage.simple.dontShowUpdateNotifs
useServerSideStorageStore().flagStorage.updateCounter < CURRENT_UPDATE_COUNTER &&
!useServerSideStorageStore().prefsStorage.simple.dontShowUpdateNotifs
}
},
methods: {
@ -46,13 +48,13 @@ const UpdateNotification = {
},
neverShowAgain () {
this.toggleShow()
this.$store.commit('setFlag', { flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER })
this.$store.commit('setPreference', { path: 'simple.dontShowUpdateNotifs', value: true })
this.$store.dispatch('pushServerSideStorage')
useServerSideStorageStore().setFlag({ flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER })
useServerSideStorageStore().setPreference({ path: 'simple.dontShowUpdateNotifs', value: true })
useServerSideStorageStore().pushServerSideStorage()
},
dismiss () {
this.$store.commit('setFlag', { flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER })
this.$store.dispatch('pushServerSideStorage')
useServerSideStorageStore().setFlag({ flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER })
useServerSideStorageStore().pushServerSideStorage()
}
},
mounted () {

View file

@ -5,7 +5,6 @@ import users from './users.js'
import api from './api.js'
import config from './config.js'
import profileConfig from './profileConfig.js'
import serverSideStorage from './serverSideStorage.js'
import adminSettings from './adminSettings.js'
import authFlow from './auth_flow.js'
import oauthTokens from './oauth_tokens.js'
@ -20,7 +19,6 @@ export default {
api,
config,
profileConfig,
serverSideStorage,
adminSettings,
authFlow,
oauthTokens,

View file

@ -4,8 +4,10 @@ import apiService from '../services/api/api.service.js'
import oauthApi from '../services/new_api/oauth.js'
import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
import { registerPushNotifications, unregisterPushNotifications } from '../services/sw/sw.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
@ -605,7 +607,8 @@ const users = {
user.muteIds = []
user.domainMutes = []
commit('setCurrentUser', user)
commit('setServerSideStorage', user)
useServerSideStorageStore().setServerSideStorage(user)
commit('addNewUsers', [user])
dispatch('fetchEmoji')
@ -615,7 +618,7 @@ const users = {
// Set our new backend interactor
commit('setBackendInteractor', backendInteractorService(accessToken))
dispatch('pushServerSideStorage')
useServerSideStorageStore().pushServerSideStorage()
if (user.token) {
dispatch('setWsToken', user.token)

View file

@ -1,3 +1,4 @@
import { defineStore } from 'pinia'
import { toRaw } from 'vue'
import {
isEqual,
@ -323,200 +324,194 @@ export const _doMigrations = (cache) => {
return cache
}
export const mutations = {
clearServerSideStorage (state) {
const blankState = { ...cloneDeep(defaultState) }
Object.keys(state).forEach(k => {
state[k] = blankState[k]
})
export const useServerSideStorageStore = defineStore('serverSideStorage', {
state() {
return cloneDeep(defaultState)
},
setServerSideStorage (state, userData) {
const live = userData.storage
state.raw = live
let cache = state.cache
if (cache && cache._user !== userData.fqn) {
console.warn('Cache belongs to another user! reinitializing local cache!')
cache = null
}
cache = _doMigrations(cache)
let { recent, stale, needUpload } = _getRecentData(cache, live)
const userNew = userData.created_at > NEW_USER_DATE
const flagsTemplate = userNew ? newUserFlags : defaultState.flagStorage
let dirty = false
if (recent === null) {
console.debug(`Data is empty, initializing for ${userNew ? 'new' : 'existing'} user`)
recent = _wrapData({
flagStorage: { ...flagsTemplate },
prefsStorage: { ...defaultState.prefsStorage }
})
}
if (!needUpload && recent && stale) {
console.debug('Checking if data needs merging...')
// discarding timestamps and versions
/* eslint-disable no-unused-vars */
const { _timestamp: _0, _version: _1, ...recentData } = recent
const { _timestamp: _2, _version: _3, ...staleData } = stale
/* eslint-enable no-unused-vars */
dirty = !isEqual(recentData, staleData)
console.debug(`Data ${dirty ? 'needs' : 'doesn\'t need'} merging`)
}
const allFlagKeys = _getAllFlags(recent, stale)
let totalFlags
let totalPrefs
if (dirty) {
// Merge the flags
console.debug('Merging the data...')
totalFlags = _mergeFlags(recent, stale, allFlagKeys)
_verifyPrefs(recent)
_verifyPrefs(stale)
totalPrefs = _mergePrefs(recent.prefsStorage, stale.prefsStorage)
} else {
totalFlags = recent.flagStorage
totalPrefs = recent.prefsStorage
}
totalFlags = _resetFlags(totalFlags)
recent.flagStorage = { ...flagsTemplate, ...totalFlags }
recent.prefsStorage = { ...defaultState.prefsStorage, ...totalPrefs }
state.dirty = dirty || needUpload
state.cache = recent
// set local timestamp to smaller one if we don't have any changes
if (stale && recent && !state.dirty) {
state.cache._timestamp = Math.min(stale._timestamp, recent._timestamp)
}
state.flagStorage = state.cache.flagStorage
state.prefsStorage = state.cache.prefsStorage
},
setFlag (state, { flag, value }) {
state.flagStorage[flag] = value
state.dirty = true
},
setPreference (state, { path, value }) {
if (path.startsWith('_')) {
throw new Error(`Tried to edit internal (starts with _) field '${path}', ignoring.`)
}
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
throw new Error(`Invalid operation 'set' for collection field '${path}', ignoring.`)
}
if (path.split(/\./g).length <= 1) {
throw new Error(`Calling set on depth <= 1 (path: ${path}) is not allowed`)
}
if (path.split(/\./g).length > 3) {
throw new Error(`Calling set on depth > 3 (path: ${path}) is not allowed`)
}
set(state.prefsStorage, path, value)
state.prefsStorage._journal = [
...state.prefsStorage._journal,
{ operation: 'set', path, args: [value], timestamp: Date.now() }
]
state.dirty = true
},
unsetPreference (state, { path, value }) {
if (path.startsWith('_')) {
throw new Error(`Tried to edit internal (starts with _) field '${path}', ignoring.`)
}
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
throw new Error(`Invalid operation 'unset' for collection field '${path}', ignoring.`)
}
if (path.split(/\./g).length <= 2) {
throw new Error(`Calling unset on depth <= 2 (path: ${path}) is not allowed`)
}
if (path.split(/\./g).length > 3) {
throw new Error(`Calling unset on depth > 3 (path: ${path}) is not allowed`)
}
unset(state.prefsStorage, path, value)
state.prefsStorage._journal = [
...state.prefsStorage._journal,
{ operation: 'unset', path, args: [], timestamp: Date.now() }
]
state.dirty = true
},
addCollectionPreference (state, { path, value }) {
if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}'`)
}
if (path.startsWith('collections')) {
const collection = new Set(get(state.prefsStorage, path))
collection.add(value)
set(state.prefsStorage, path, [...collection])
} else if (path.startsWith('objectCollections')) {
const { _key } = value
if (!_key && typeof _key !== 'string') {
throw new Error('Object for storage is missing _key field!')
}
const collection = new Set(get(state.prefsStorage, path + '.index'))
collection.add(_key)
set(state.prefsStorage, path + '.index', [...collection])
set(state.prefsStorage, path + '.data.' + _key, value)
}
state.prefsStorage._journal = [
...state.prefsStorage._journal,
{ operation: 'addToCollection', path, args: [value], timestamp: Date.now() }
]
state.dirty = true
},
removeCollectionPreference (state, { path, value }) {
if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
}
const collection = new Set(get(state.prefsStorage, path))
collection.delete(value)
set(state.prefsStorage, path, [...collection])
state.prefsStorage._journal = [
...state.prefsStorage._journal,
{ operation: 'removeFromCollection', path, args: [value], timestamp: Date.now() }
]
state.dirty = true
},
reorderCollectionPreference (state, { path, value, movement }) {
if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
}
const collection = get(state.prefsStorage, path)
const newCollection = _moveItemInArray(collection, value, movement)
set(state.prefsStorage, path, newCollection)
state.prefsStorage._journal = [
...state.prefsStorage._journal,
{ operation: 'arrangeCollection', path, args: [value], timestamp: Date.now() }
]
state.dirty = true
},
updateCache (state, { username }) {
state.prefsStorage._journal = _mergeJournal(state.prefsStorage._journal)
state.cache = _wrapData({
flagStorage: toRaw(state.flagStorage),
prefsStorage: toRaw(state.prefsStorage)
}, username)
}
}
const serverSideStorage = {
state: {
...cloneDeep(defaultState)
},
mutations,
actions: {
pushServerSideStorage ({ state, rootState, commit }, { force = false } = {}) {
const needPush = state.dirty || force
clearServerSideStorage () {
const blankState = { ...cloneDeep(defaultState) }
Object.keys(this).forEach(k => {
this[k] = blankState[k]
})
},
setServerSideStorage (userData) {
const live = userData.storage
this.raw = live
let cache = this.cache
if (cache && cache._user !== userData.fqn) {
console.warn('Cache belongs to another user! reinitializing local cache!')
cache = null
}
cache = _doMigrations(cache)
let { recent, stale, needUpload } = _getRecentData(cache, live)
const userNew = userData.created_at > NEW_USER_DATE
const flagsTemplate = userNew ? newUserFlags : defaultState.flagStorage
let dirty = false
if (recent === null) {
console.debug(`Data is empty, initializing for ${userNew ? 'new' : 'existing'} user`)
recent = _wrapData({
flagStorage: { ...flagsTemplate },
prefsStorage: { ...defaultState.prefsStorage }
})
}
if (!needUpload && recent && stale) {
console.debug('Checking if data needs merging...')
// discarding timestamps and versions
/* eslint-disable no-unused-vars */
const { _timestamp: _0, _version: _1, ...recentData } = recent
const { _timestamp: _2, _version: _3, ...staleData } = stale
/* eslint-enable no-unused-vars */
dirty = !isEqual(recentData, staleData)
console.debug(`Data ${dirty ? 'needs' : 'doesn\'t need'} merging`)
}
const allFlagKeys = _getAllFlags(recent, stale)
let totalFlags
let totalPrefs
if (dirty) {
// Merge the flags
console.debug('Merging the data...')
totalFlags = _mergeFlags(recent, stale, allFlagKeys)
_verifyPrefs(recent)
_verifyPrefs(stale)
totalPrefs = _mergePrefs(recent.prefsStorage, stale.prefsStorage)
} else {
totalFlags = recent.flagStorage
totalPrefs = recent.prefsStorage
}
totalFlags = _resetFlags(totalFlags)
recent.flagStorage = { ...flagsTemplate, ...totalFlags }
recent.prefsStorage = { ...defaultState.prefsStorage, ...totalPrefs }
this.dirty = dirty || needUpload
this.cache = recent
// set local timestamp to smaller one if we don't have any changes
if (stale && recent && !this.dirty) {
this.cache._timestamp = Math.min(stale._timestamp, recent._timestamp)
}
this.flagStorage = this.cache.flagStorage
this.prefsStorage = this.cache.prefsStorage
},
setFlag ({ flag, value }) {
this.flagStorage[flag] = value
this.dirty = true
},
setPreference ({ path, value }) {
if (path.startsWith('_')) {
throw new Error(`Tried to edit internal (starts with _) field '${path}', ignoring.`)
}
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
throw new Error(`Invalid operation 'set' for collection field '${path}', ignoring.`)
}
if (path.split(/\./g).length <= 1) {
throw new Error(`Calling set on depth <= 1 (path: ${path}) is not allowed`)
}
if (path.split(/\./g).length > 3) {
throw new Error(`Calling set on depth > 3 (path: ${path}) is not allowed`)
}
set(this.prefsStorage, path, value)
this.prefsStorage._journal = [
...this.prefsStorage._journal,
{ operation: 'set', path, args: [value], timestamp: Date.now() }
]
this.dirty = true
},
unsetPreference ({ path, value }) {
if (path.startsWith('_')) {
throw new Error(`Tried to edit internal (starts with _) field '${path}', ignoring.`)
}
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
throw new Error(`Invalid operation 'unset' for collection field '${path}', ignoring.`)
}
if (path.split(/\./g).length <= 2) {
throw new Error(`Calling unset on depth <= 2 (path: ${path}) is not allowed`)
}
if (path.split(/\./g).length > 3) {
throw new Error(`Calling unset on depth > 3 (path: ${path}) is not allowed`)
}
unset(this.prefsStorage, path, value)
this.prefsStorage._journal = [
...this.prefsStorage._journal,
{ operation: 'unset', path, args: [], timestamp: Date.now() }
]
this.dirty = true
},
addCollectionPreference ({ path, value }) {
if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}'`)
}
if (path.startsWith('collections')) {
const collection = new Set(get(this.prefsStorage, path))
collection.add(value)
set(this.prefsStorage, path, [...collection])
} else if (path.startsWith('objectCollections')) {
const { _key } = value
if (!_key && typeof _key !== 'string') {
throw new Error('Object for storage is missing _key field!')
}
const collection = new Set(get(this.prefsStorage, path + '.index'))
collection.add(_key)
set(this.prefsStorage, path + '.index', [...collection])
set(this.prefsStorage, path + '.data.' + _key, value)
}
this.prefsStorage._journal = [
...this.prefsStorage._journal,
{ operation: 'addToCollection', path, args: [value], timestamp: Date.now() }
]
this.dirty = true
},
removeCollectionPreference ({ path, value }) {
if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
}
const collection = new Set(get(this.prefsStorage, path))
collection.delete(value)
set(this.prefsStorage, path, [...collection])
this.prefsStorage._journal = [
...this.prefsStorage._journal,
{ operation: 'removeFromCollection', path, args: [value], timestamp: Date.now() }
]
this.dirty = true
},
reorderCollectionPreference ({ path, value, movement }) {
if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
}
const collection = get(this.prefsStorage, path)
const newCollection = _moveItemInArray(collection, value, movement)
set(this.prefsStorage, path, newCollection)
this.prefsStorage._journal = [
...this.prefsStorage._journal,
{ operation: 'arrangeCollection', path, args: [value], timestamp: Date.now() }
]
this.dirty = true
},
updateCache ({ username }) {
this.prefsStorage._journal = _mergeJournal(this.prefsStorage._journal)
this.cache = _wrapData({
flagStorage: toRaw(this.flagStorage),
prefsStorage: toRaw(this.prefsStorage)
}, username)
},
pushServerSideStorage ({ force = false } = {}) {
const needPush = this.dirty || force
if (!needPush) return
commit('updateCache', { username: rootState.users.currentUser.fqn })
const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } }
rootState.api.backendInteractor
this.updateCache({ username: window.vuex.state.users.currentUser.fqn })
const params = { pleroma_settings_store: { 'pleroma-fe': this.cache } }
window.vuex.state.api.backendInteractor
.updateProfile({ params })
.then((user) => {
commit('setServerSideStorage', user)
state.dirty = false
this.setServerSideStorage(user)
this.dirty = false
})
}
}
}
export default serverSideStorage
})

View file

@ -1,4 +1,5 @@
import { cloneDeep } from 'lodash'
import { setActivePinia, createPinia } from 'pinia'
import {
VERSION,
@ -10,49 +11,50 @@ import {
_mergeFlags,
_mergePrefs,
_resetFlags,
mutations,
defaultState,
newUserFlags
} from 'src/modules/serverSideStorage.js'
newUserFlags,
useServerSideStorageStore,
} from 'src/stores/serverSideStorage.js'
describe('The serverSideStorage module', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
describe('mutations', () => {
describe('setServerSideStorage', () => {
const { setServerSideStorage } = mutations
const user = {
created_at: new Date('1999-02-09'),
storage: {}
}
it('should initialize storage if none present', () => {
const state = cloneDeep(defaultState)
setServerSideStorage(state, user)
expect(state.cache._version).to.eql(VERSION)
expect(state.cache._timestamp).to.be.a('number')
expect(state.cache.flagStorage).to.eql(defaultState.flagStorage)
expect(state.cache.prefsStorage).to.eql(defaultState.prefsStorage)
const store = useServerSideStorageStore()
store.setServerSideStorage(store, user)
expect(store.cache._version).to.eql(VERSION)
expect(store.cache._timestamp).to.be.a('number')
expect(store.cache.flagStorage).to.eql(defaultState.flagStorage)
expect(store.cache.prefsStorage).to.eql(defaultState.prefsStorage)
})
it('should initialize storage with proper flags for new users if none present', () => {
const state = cloneDeep(defaultState)
setServerSideStorage(state, { ...user, created_at: new Date() })
expect(state.cache._version).to.eql(VERSION)
expect(state.cache._timestamp).to.be.a('number')
expect(state.cache.flagStorage).to.eql(newUserFlags)
expect(state.cache.prefsStorage).to.eql(defaultState.prefsStorage)
const store = useServerSideStorageStore()
store.setServerSideStorage({ ...user, created_at: new Date() })
expect(store.cache._version).to.eql(VERSION)
expect(store.cache._timestamp).to.be.a('number')
expect(store.cache.flagStorage).to.eql(newUserFlags)
expect(store.cache.prefsStorage).to.eql(defaultState.prefsStorage)
})
it('should merge flags even if remote timestamp is older', () => {
const state = {
...cloneDeep(defaultState),
cache: {
_timestamp: Date.now(),
_version: VERSION,
...cloneDeep(defaultState)
}
const store = useServerSideStorageStore()
store.cache = {
_timestamp: Date.now(),
_version: VERSION,
...cloneDeep(defaultState)
}
setServerSideStorage(
state,
store.setServerSideStorage(
{
...user,
storage: {
@ -68,19 +70,18 @@ describe('The serverSideStorage module', () => {
}
}
)
expect(state.cache.flagStorage).to.eql({
expect(store.cache.flagStorage).to.eql({
...defaultState.flagStorage,
updateCounter: 1
})
})
it('should reset local timestamp to remote if contents are the same', () => {
const state = {
...cloneDeep(defaultState),
cache: null
}
setServerSideStorage(
state,
const store = useServerSideStorageStore()
store.cache = null
store.setServerSideStorage(
{
...user,
storage: {
@ -93,97 +94,95 @@ describe('The serverSideStorage module', () => {
}
}
)
expect(state.cache._timestamp).to.eql(123)
expect(state.flagStorage.updateCounter).to.eql(999)
expect(state.cache.flagStorage.updateCounter).to.eql(999)
expect(store.cache._timestamp).to.eql(123)
expect(store.flagStorage.updateCounter).to.eql(999)
expect(store.cache.flagStorage.updateCounter).to.eql(999)
})
it('should remote version if local missing', () => {
const state = cloneDeep(defaultState)
setServerSideStorage(state, user)
expect(state.cache._version).to.eql(VERSION)
expect(state.cache._timestamp).to.be.a('number')
expect(state.cache.flagStorage).to.eql(defaultState.flagStorage)
const store = useServerSideStorageStore()
store.setServerSideStorage(store, user)
expect(store.cache._version).to.eql(VERSION)
expect(store.cache._timestamp).to.be.a('number')
expect(store.cache.flagStorage).to.eql(defaultState.flagStorage)
})
})
describe('setPreference', () => {
const { setPreference, unsetPreference, updateCache, addCollectionPreference, removeCollectionPreference } = mutations
it('should set preference and update journal log accordingly', () => {
const state = cloneDeep(defaultState)
setPreference(state, { path: 'simple.testing', value: 1 })
expect(state.prefsStorage.simple.testing).to.eql(1)
expect(state.prefsStorage._journal.length).to.eql(1)
expect(state.prefsStorage._journal[0]).to.eql({
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.testing', value: 1 })
expect(store.prefsStorage.simple.testing).to.eql(1)
expect(store.prefsStorage._journal.length).to.eql(1)
expect(store.prefsStorage._journal[0]).to.eql({
path: 'simple.testing',
operation: 'set',
args: [1],
// should have A timestamp, we don't really care what it is
timestamp: state.prefsStorage._journal[0].timestamp
timestamp: store.prefsStorage._journal[0].timestamp
})
})
it('should keep journal to a minimum', () => {
const state = cloneDeep(defaultState)
setPreference(state, { path: 'simple.testing', value: 1 })
setPreference(state, { path: 'simple.testing', value: 2 })
addCollectionPreference(state, { path: 'collections.testing', value: 2 })
removeCollectionPreference(state, { path: 'collections.testing', value: 2 })
updateCache(state, { username: 'test' })
expect(state.prefsStorage.simple.testing).to.eql(2)
expect(state.prefsStorage.collections.testing).to.eql([])
expect(state.prefsStorage._journal.length).to.eql(2)
expect(state.prefsStorage._journal[0]).to.eql({
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.testing', value: 1 })
store.setPreference({ path: 'simple.testing', value: 2 })
store.addCollectionPreference({ path: 'collections.testing', value: 2 })
store.removeCollectionPreference({ path: 'collections.testing', value: 2 })
store.updateCache({ username: 'test' })
expect(store.prefsStorage.simple.testing).to.eql(2)
expect(store.prefsStorage.collections.testing).to.eql([])
expect(store.prefsStorage._journal.length).to.eql(2)
expect(store.prefsStorage._journal[0]).to.eql({
path: 'simple.testing',
operation: 'set',
args: [2],
// should have A timestamp, we don't really care what it is
timestamp: state.prefsStorage._journal[0].timestamp
timestamp: store.prefsStorage._journal[0].timestamp
})
expect(state.prefsStorage._journal[1]).to.eql({
expect(store.prefsStorage._journal[1]).to.eql({
path: 'collections.testing',
operation: 'removeFromCollection',
args: [2],
// should have A timestamp, we don't really care what it is
timestamp: state.prefsStorage._journal[1].timestamp
timestamp: store.prefsStorage._journal[1].timestamp
})
})
it('should remove duplicate entries from journal', () => {
const state = cloneDeep(defaultState)
setPreference(state, { path: 'simple.testing', value: 1 })
setPreference(state, { path: 'simple.testing', value: 1 })
addCollectionPreference(state, { path: 'collections.testing', value: 2 })
addCollectionPreference(state, { path: 'collections.testing', value: 2 })
updateCache(state, { username: 'test' })
expect(state.prefsStorage.simple.testing).to.eql(1)
expect(state.prefsStorage.collections.testing).to.eql([2])
expect(state.prefsStorage._journal.length).to.eql(2)
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.testing', value: 1 })
store.setPreference({ path: 'simple.testing', value: 1 })
store.addCollectionPreference({ path: 'collections.testing', value: 2 })
store.addCollectionPreference({ path: 'collections.testing', value: 2 })
store.updateCache({ username: 'test' })
expect(store.prefsStorage.simple.testing).to.eql(1)
expect(store.prefsStorage.collections.testing).to.eql([2])
expect(store.prefsStorage._journal.length).to.eql(2)
})
it('should remove depth = 3 set/unset entries from journal', () => {
const state = cloneDeep(defaultState)
setPreference(state, { path: 'simple.object.foo', value: 1 })
unsetPreference(state, { path: 'simple.object.foo' })
updateCache(state, { username: 'test' })
expect(state.prefsStorage.simple.object).to.not.have.property('foo')
expect(state.prefsStorage._journal.length).to.eql(1)
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.object.foo', value: 1 })
store.unsetPreference({ path: 'simple.object.foo' })
store.updateCache(store, { username: 'test' })
expect(store.prefsStorage.simple.object).to.not.have.property('foo')
expect(store.prefsStorage._journal.length).to.eql(1)
})
it('should not allow unsetting depth <= 2', () => {
const state = cloneDeep(defaultState)
setPreference(state, { path: 'simple.object.foo', value: 1 })
expect(() => unsetPreference(state, { path: 'simple' })).to.throw()
expect(() => unsetPreference(state, { path: 'simple.object' })).to.throw()
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.object.foo', value: 1 })
expect(() => store.unsetPreference({ path: 'simple' })).to.throw()
expect(() => store.unsetPreference({ path: 'simple.object' })).to.throw()
})
it('should not allow (un)setting depth > 3', () => {
const state = cloneDeep(defaultState)
setPreference(state, { path: 'simple.object', value: {} })
expect(() => setPreference(state, { path: 'simple.object.lv3', value: 1 })).to.not.throw()
expect(() => setPreference(state, { path: 'simple.object.lv3.lv4', value: 1})).to.throw()
expect(() => unsetPreference(state, { path: 'simple.object.lv3', value: 1 })).to.not.throw()
expect(() => unsetPreference(state, { path: 'simple.object.lv3.lv4', value: 1})).to.throw()
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.object', value: {} })
expect(() => store.setPreference({ path: 'simple.object.lv3', value: 1 })).to.not.throw()
expect(() => store.setPreference({ path: 'simple.object.lv3.lv4', value: 1})).to.throw()
expect(() => store.unsetPreference({ path: 'simple.object.lv3', value: 1 })).to.not.throw()
expect(() => store.unsetPreference({ path: 'simple.object.lv3.lv4', value: 1})).to.throw()
})
})
})