migrate to pinia

This commit is contained in:
Henry Jameson 2025-03-23 20:03:25 +02:00
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 SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue' import Notifications from '../notifications/notifications.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.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 { import {
unseenNotificationsFromStore, unseenNotificationsFromStore,
countExtraNotifications countExtraNotifications
} from '../../services/notification_utils/notification_utils' } 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 { mapGetters } from 'vuex'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { useAnnouncementsStore } from 'src/stores/announcements'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faTimes, faTimes,
@ -18,7 +23,6 @@ import {
faMinus, faMinus,
faCheckDouble faCheckDouble
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useAnnouncementsStore } from 'src/stores/announcements'
library.add( library.add(
faTimes, faTimes,
@ -71,10 +75,9 @@ const MobileNav = {
return this.$route.name === 'chat' return this.$route.name === 'chat'
}, },
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
...mapGetters(['unreadChatCount']), ...mapState(useServerSideStorageStore, {
chatsPinned () { pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems).has('chats')
return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats') }),
},
shouldConfirmLogout () { shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout 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 NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import NavigationPins from 'src/components/navigation/navigation_pins.vue' import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import { useAnnouncementsStore } from 'src/stores/announcements' import { useAnnouncementsStore } from 'src/stores/announcements'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -76,19 +78,19 @@ const NavPanel = {
this.editMode = !this.editMode this.editMode = !this.editMode
}, },
toggleCollapse () { toggleCollapse () {
this.$store.commit('setPreference', { path: 'simple.collapseNav', value: !this.collapsed }) useServerSideStorageStore().setPreference({ path: 'simple.collapseNav', value: !this.collapsed })
this.$store.dispatch('pushServerSideStorage') useServerSideStorageStore().pushServerSideStorage()
}, },
isPinned (item) { isPinned (item) {
return this.pinnedItems.has(item) return this.pinnedItems.has(item)
}, },
togglePin (item) { togglePin (item) {
if (this.isPinned(item)) { if (this.isPinned(item)) {
this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value: item }) useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value: item })
} else { } else {
this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value: item }) useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value: item })
} }
this.$store.dispatch('pushServerSideStorage') useServerSideStorageStore().pushServerSideStorage()
} }
}, },
computed: { computed: {
@ -96,14 +98,16 @@ const NavPanel = {
unreadAnnouncementCount: 'unreadAnnouncementCount', unreadAnnouncementCount: 'unreadAnnouncementCount',
supportsAnnouncements: store => store.supportsAnnouncements supportsAnnouncements: store => store.supportsAnnouncements
}), }),
...mapPiniaState(useServerSideStorageStore, {
collapsed: store => store.prefsStorage.simple.collapseNav,
pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems)
}),
...mapState({ ...mapState({
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length, followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating, federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, 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 bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
}), }),
timelinesItems () { 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 OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faThumbtack } from '@fortawesome/free-solid-svg-icons' 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 { useAnnouncementsStore } from 'src/stores/announcements'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
library.add(faThumbtack) library.add(faThumbtack)
@ -19,11 +21,11 @@ const NavigationEntry = {
}, },
togglePin (value) { togglePin (value) {
if (this.isPinned(value)) { if (this.isPinned(value)) {
this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value }) useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value })
} else { } else {
this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value }) useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value })
} }
this.$store.dispatch('pushServerSideStorage') useServerSideStorageStore().pushServerSideStorage()
} }
}, },
computed: { computed: {
@ -35,9 +37,11 @@ const NavigationEntry = {
}, },
...mapStores(useAnnouncementsStore), ...mapStores(useAnnouncementsStore),
...mapState({ ...mapState({
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems) }),
}) ...mapPiniaState(useServerSideStorageStore, {
pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems)
}),
} }
} }

View file

@ -20,6 +20,7 @@ import {
import { useListsStore } from 'src/stores/lists' import { useListsStore } from 'src/stores/lists'
import { useAnnouncementsStore } from 'src/stores/announcements' import { useAnnouncementsStore } from 'src/stores/announcements'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
library.add( library.add(
faUsers, faUsers,
@ -54,15 +55,17 @@ const NavPanel = {
supportsAnnouncements: store => store.supportsAnnouncements supportsAnnouncements: store => store.supportsAnnouncements
}), }),
...mapPiniaState(useBookmarkFoldersStore, { ...mapPiniaState(useBookmarkFoldersStore, {
bookmarks: getBookmarkFolderEntries bookmarks: getBookmarkFolderEntries,
}),
...mapPiniaState(useServerSideStorageStore, {
pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems)
}), }),
...mapState({ ...mapState({
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length, followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating, federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
}), }),
pinnedList () { pinnedList () {
if (!this.currentUser) { 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 ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
import ActionButtonContainer from './action_button_container.vue' import ActionButtonContainer from './action_button_container.vue'
import Popover from 'src/components/popover/popover.vue' import Popover from 'src/components/popover/popover.vue'
import genRandomSeed from 'src/services/random_seed/random_seed.service.js' import genRandomSeed from 'src/services/random_seed/random_seed.service.js'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { BUTTONS } from './buttons_definitions.js' import { BUTTONS } from './buttons_definitions.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -36,8 +38,8 @@ const StatusActionButtons = {
ActionButtonContainer ActionButtonContainer
}, },
computed: { computed: {
...mapState({ ...mapState(useServerSideStorageStore, {
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedStatusActions) pinnedItems: store => new Set(store.prefsStorage.collections.pinnedStatusActions)
}), }),
buttons () { buttons () {
return BUTTONS.filter(x => x.if ? x.if(this.funcArg) : true) return BUTTONS.filter(x => x.if ? x.if(this.funcArg) : true)
@ -101,12 +103,12 @@ const StatusActionButtons = {
return this.pinnedItems.has(button.name) return this.pinnedItems.has(button.name)
}, },
unpin (button) { unpin (button) {
this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedStatusActions', value: button.name }) useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedStatusActions', value: button.name })
this.$store.dispatch('pushServerSideStorage') useServerSideStorageStore().pushServerSideStorage()
}, },
pin (button) { pin (button) {
this.$store.commit('addCollectionPreference', { path: 'collections.pinnedStatusActions', value: button.name }) useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedStatusActions', value: button.name })
this.$store.dispatch('pushServerSideStorage') useServerSideStorageStore().pushServerSideStorage()
}, },
getComponent (button) { getComponent (button) {
if (!this.$store.state.users.currentUser && button.anonLink) { 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 pleromaTanMask from 'src/assets/pleromatan_apology_mask.png'
import pleromaTanFoxMask from 'src/assets/pleromatan_apology_fox_mask.png' import pleromaTanFoxMask from 'src/assets/pleromatan_apology_fox_mask.png'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { import {
faTimes faTimes
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
@ -36,8 +38,8 @@ const UpdateNotification = {
shouldShow () { shouldShow () {
return !this.$store.state.instance.disableUpdateNotification && return !this.$store.state.instance.disableUpdateNotification &&
this.$store.state.users.currentUser && this.$store.state.users.currentUser &&
this.$store.state.serverSideStorage.flagStorage.updateCounter < CURRENT_UPDATE_COUNTER && useServerSideStorageStore().flagStorage.updateCounter < CURRENT_UPDATE_COUNTER &&
!this.$store.state.serverSideStorage.prefsStorage.simple.dontShowUpdateNotifs !useServerSideStorageStore().prefsStorage.simple.dontShowUpdateNotifs
} }
}, },
methods: { methods: {
@ -46,13 +48,13 @@ const UpdateNotification = {
}, },
neverShowAgain () { neverShowAgain () {
this.toggleShow() this.toggleShow()
this.$store.commit('setFlag', { flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER }) useServerSideStorageStore().setFlag({ flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER })
this.$store.commit('setPreference', { path: 'simple.dontShowUpdateNotifs', value: true }) useServerSideStorageStore().setPreference({ path: 'simple.dontShowUpdateNotifs', value: true })
this.$store.dispatch('pushServerSideStorage') useServerSideStorageStore().pushServerSideStorage()
}, },
dismiss () { dismiss () {
this.$store.commit('setFlag', { flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER }) useServerSideStorageStore().setFlag({ flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER })
this.$store.dispatch('pushServerSideStorage') useServerSideStorageStore().pushServerSideStorage()
} }
}, },
mounted () { mounted () {

View file

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

View file

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

View file

@ -1,3 +1,4 @@
import { defineStore } from 'pinia'
import { toRaw } from 'vue' import { toRaw } from 'vue'
import { import {
isEqual, isEqual,
@ -323,17 +324,21 @@ export const _doMigrations = (cache) => {
return cache return cache
} }
export const mutations = { export const useServerSideStorageStore = defineStore('serverSideStorage', {
clearServerSideStorage (state) { state() {
return cloneDeep(defaultState)
},
actions: {
clearServerSideStorage () {
const blankState = { ...cloneDeep(defaultState) } const blankState = { ...cloneDeep(defaultState) }
Object.keys(state).forEach(k => { Object.keys(this).forEach(k => {
state[k] = blankState[k] this[k] = blankState[k]
}) })
}, },
setServerSideStorage (state, userData) { setServerSideStorage (userData) {
const live = userData.storage const live = userData.storage
state.raw = live this.raw = live
let cache = state.cache let cache = this.cache
if (cache && cache._user !== userData.fqn) { if (cache && cache._user !== userData.fqn) {
console.warn('Cache belongs to another user! reinitializing local cache!') console.warn('Cache belongs to another user! reinitializing local cache!')
cache = null cache = null
@ -386,20 +391,20 @@ export const mutations = {
recent.flagStorage = { ...flagsTemplate, ...totalFlags } recent.flagStorage = { ...flagsTemplate, ...totalFlags }
recent.prefsStorage = { ...defaultState.prefsStorage, ...totalPrefs } recent.prefsStorage = { ...defaultState.prefsStorage, ...totalPrefs }
state.dirty = dirty || needUpload this.dirty = dirty || needUpload
state.cache = recent this.cache = recent
// set local timestamp to smaller one if we don't have any changes // set local timestamp to smaller one if we don't have any changes
if (stale && recent && !state.dirty) { if (stale && recent && !this.dirty) {
state.cache._timestamp = Math.min(stale._timestamp, recent._timestamp) this.cache._timestamp = Math.min(stale._timestamp, recent._timestamp)
} }
state.flagStorage = state.cache.flagStorage this.flagStorage = this.cache.flagStorage
state.prefsStorage = state.cache.prefsStorage this.prefsStorage = this.cache.prefsStorage
}, },
setFlag (state, { flag, value }) { setFlag ({ flag, value }) {
state.flagStorage[flag] = value this.flagStorage[flag] = value
state.dirty = true this.dirty = true
}, },
setPreference (state, { path, value }) { setPreference ({ path, value }) {
if (path.startsWith('_')) { if (path.startsWith('_')) {
throw new Error(`Tried to edit internal (starts with _) field '${path}', ignoring.`) throw new Error(`Tried to edit internal (starts with _) field '${path}', ignoring.`)
} }
@ -412,14 +417,14 @@ export const mutations = {
if (path.split(/\./g).length > 3) { if (path.split(/\./g).length > 3) {
throw new Error(`Calling set on depth > 3 (path: ${path}) is not allowed`) throw new Error(`Calling set on depth > 3 (path: ${path}) is not allowed`)
} }
set(state.prefsStorage, path, value) set(this.prefsStorage, path, value)
state.prefsStorage._journal = [ this.prefsStorage._journal = [
...state.prefsStorage._journal, ...this.prefsStorage._journal,
{ operation: 'set', path, args: [value], timestamp: Date.now() } { operation: 'set', path, args: [value], timestamp: Date.now() }
] ]
state.dirty = true this.dirty = true
}, },
unsetPreference (state, { path, value }) { unsetPreference ({ path, value }) {
if (path.startsWith('_')) { if (path.startsWith('_')) {
throw new Error(`Tried to edit internal (starts with _) field '${path}', ignoring.`) throw new Error(`Tried to edit internal (starts with _) field '${path}', ignoring.`)
} }
@ -432,91 +437,81 @@ export const mutations = {
if (path.split(/\./g).length > 3) { if (path.split(/\./g).length > 3) {
throw new Error(`Calling unset on depth > 3 (path: ${path}) is not allowed`) throw new Error(`Calling unset on depth > 3 (path: ${path}) is not allowed`)
} }
unset(state.prefsStorage, path, value) unset(this.prefsStorage, path, value)
state.prefsStorage._journal = [ this.prefsStorage._journal = [
...state.prefsStorage._journal, ...this.prefsStorage._journal,
{ operation: 'unset', path, args: [], timestamp: Date.now() } { operation: 'unset', path, args: [], timestamp: Date.now() }
] ]
state.dirty = true this.dirty = true
}, },
addCollectionPreference (state, { path, value }) { addCollectionPreference ({ path, value }) {
if (path.startsWith('_')) { if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}'`) throw new Error(`tried to edit internal (starts with _) field '${path}'`)
} }
if (path.startsWith('collections')) { if (path.startsWith('collections')) {
const collection = new Set(get(state.prefsStorage, path)) const collection = new Set(get(this.prefsStorage, path))
collection.add(value) collection.add(value)
set(state.prefsStorage, path, [...collection]) set(this.prefsStorage, path, [...collection])
} else if (path.startsWith('objectCollections')) { } else if (path.startsWith('objectCollections')) {
const { _key } = value const { _key } = value
if (!_key && typeof _key !== 'string') { if (!_key && typeof _key !== 'string') {
throw new Error('Object for storage is missing _key field!') throw new Error('Object for storage is missing _key field!')
} }
const collection = new Set(get(state.prefsStorage, path + '.index')) const collection = new Set(get(this.prefsStorage, path + '.index'))
collection.add(_key) collection.add(_key)
set(state.prefsStorage, path + '.index', [...collection]) set(this.prefsStorage, path + '.index', [...collection])
set(state.prefsStorage, path + '.data.' + _key, value) set(this.prefsStorage, path + '.data.' + _key, value)
} }
state.prefsStorage._journal = [ this.prefsStorage._journal = [
...state.prefsStorage._journal, ...this.prefsStorage._journal,
{ operation: 'addToCollection', path, args: [value], timestamp: Date.now() } { operation: 'addToCollection', path, args: [value], timestamp: Date.now() }
] ]
state.dirty = true this.dirty = true
}, },
removeCollectionPreference (state, { path, value }) { removeCollectionPreference ({ path, value }) {
if (path.startsWith('_')) { if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}', ignoring.`) throw new Error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
} }
const collection = new Set(get(state.prefsStorage, path)) const collection = new Set(get(this.prefsStorage, path))
collection.delete(value) collection.delete(value)
set(state.prefsStorage, path, [...collection]) set(this.prefsStorage, path, [...collection])
state.prefsStorage._journal = [ this.prefsStorage._journal = [
...state.prefsStorage._journal, ...this.prefsStorage._journal,
{ operation: 'removeFromCollection', path, args: [value], timestamp: Date.now() } { operation: 'removeFromCollection', path, args: [value], timestamp: Date.now() }
] ]
state.dirty = true this.dirty = true
}, },
reorderCollectionPreference (state, { path, value, movement }) { reorderCollectionPreference ({ path, value, movement }) {
if (path.startsWith('_')) { if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}', ignoring.`) throw new Error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
} }
const collection = get(state.prefsStorage, path) const collection = get(this.prefsStorage, path)
const newCollection = _moveItemInArray(collection, value, movement) const newCollection = _moveItemInArray(collection, value, movement)
set(state.prefsStorage, path, newCollection) set(this.prefsStorage, path, newCollection)
state.prefsStorage._journal = [ this.prefsStorage._journal = [
...state.prefsStorage._journal, ...this.prefsStorage._journal,
{ operation: 'arrangeCollection', path, args: [value], timestamp: Date.now() } { operation: 'arrangeCollection', path, args: [value], timestamp: Date.now() }
] ]
state.dirty = true this.dirty = true
}, },
updateCache (state, { username }) { updateCache ({ username }) {
state.prefsStorage._journal = _mergeJournal(state.prefsStorage._journal) this.prefsStorage._journal = _mergeJournal(this.prefsStorage._journal)
state.cache = _wrapData({ this.cache = _wrapData({
flagStorage: toRaw(state.flagStorage), flagStorage: toRaw(this.flagStorage),
prefsStorage: toRaw(state.prefsStorage) prefsStorage: toRaw(this.prefsStorage)
}, username) }, username)
}
}
const serverSideStorage = {
state: {
...cloneDeep(defaultState)
}, },
mutations, pushServerSideStorage ({ force = false } = {}) {
actions: { const needPush = this.dirty || force
pushServerSideStorage ({ state, rootState, commit }, { force = false } = {}) {
const needPush = state.dirty || force
if (!needPush) return if (!needPush) return
commit('updateCache', { username: rootState.users.currentUser.fqn }) this.updateCache({ username: window.vuex.state.users.currentUser.fqn })
const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } } const params = { pleroma_settings_store: { 'pleroma-fe': this.cache } }
rootState.api.backendInteractor window.vuex.state.api.backendInteractor
.updateProfile({ params }) .updateProfile({ params })
.then((user) => { .then((user) => {
commit('setServerSideStorage', user) this.setServerSideStorage(user)
state.dirty = false this.dirty = false
}) })
} }
} }
} })
export default serverSideStorage

View file

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