akkoma-fe/src/modules/config.js
Oneric e274adf47d Fix setting restore from file
Previously restoring from file would also restore the old version value
breaking upload of the new settings to the server.

Additionallly it didn’t even attempt to sync settings after restore and
was insufferably slow due to individually updating every single setting
with a dispatch. Instead only update changed settings like on server
syncs which usually speeds the process up considerably.

Fixes: https://akkoma.dev/AkkomaGang/akkoma-fe/issues/405
2024-07-06 01:59:42 +02:00

282 lines
9.8 KiB
JavaScript

import Cookies from 'js-cookie'
import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages'
import localeService from '../services/locale/locale.service.js'
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
const browserLocale = (window.navigator.language || 'en').split('-')[0]
/* TODO this is a bit messy.
* We need to declare settings with their types and also deal with
* instance-default settings in some way, hopefully try to avoid copy-pasta
* in general.
*/
export const multiChoiceProperties = [
'postContentType',
'subjectLineBehavior',
'conversationDisplay', // tree | linear
'conversationOtherRepliesButton', // below | inside
'mentionLinkDisplay' // short | full_for_remote | full
]
export const defaultState = {
profile: 'default',
profileVersion: 0, // internal fe copy of server-side version
expertLevel: 0, // used to track which settings to show and hide
colors: {},
theme: undefined,
customTheme: undefined,
customThemeSource: undefined,
hideISP: false,
hideInstanceWallpaper: false,
// bad name: actually hides posts of muted USERS
hideMutedPosts: undefined, // instance default
hideMutedThreads: undefined, // instance default
hideThreadsWithBlockedUsers: undefined, // instance default
hideWordFilteredPosts: undefined, // instance default
muteBotStatuses: undefined, // instance default
collapseMessageWithSubject: true, // instance default
padEmoji: true,
showNavShortcuts: undefined, // instance default
showPanelNavShortcuts: undefined, // instance default
showWiderShortcuts: undefined, // instance default
hideSiteFavicon: undefined, // instance default
hideSiteName: undefined, // instance default
hideAttachments: false,
hideAttachmentsInConv: false,
maxThumbnails: 16,
hideNsfw: true,
preloadImage: true,
loopVideo: true,
loopVideoSilentOnly: true,
streaming: false,
emojiReactionsOnTimeline: true,
alwaysShowNewPostButton: false,
autohideFloatingPostButton: false,
pauseOnUnfocused: true,
displayPageBackgrounds: true,
stopGifs: undefined,
replyVisibility: 'all',
thirdColumnMode: 'notifications',
notificationVisibility: {
follows: true,
mentions: true,
likes: true,
repeats: true,
moves: true,
emojiReactions: true,
followRequest: true,
polls: true
},
webPushNotifications: false,
webPushHideIfCW: true,
muteWords: [],
highlight: {},
interfaceLanguage: browserLocale,
hideScopeNotice: false,
useStreamingApi: false,
sidebarRight: undefined, // instance default
subjectLineBehavior: undefined, // instance default
alwaysShowSubjectInput: undefined, // instance default
postContentType: undefined, // instance default
// This hides statuses filtered via a word filter
hideFilteredStatuses: undefined, // instance default
modalOnRepeat: undefined, // instance default
modalOnUnfollow: undefined, // instance default
modalOnBlock: undefined, // instance default
modalOnMute: undefined, // instance default
modalOnDelete: undefined, // instance default
modalOnLogout: undefined, // instance default
modalOnApproveFollow: undefined, // instance default
modalOnDenyFollow: undefined, // instance default
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: true,
disableStickyHeaders: false,
showScrollbars: false,
greentext: undefined, // instance default
mentionLinkDisplay: undefined, // instance default
mentionLinkShowTooltip: undefined, // instance default
mentionLinkShowAvatar: undefined, // instance default
mentionLinkFadeDomain: undefined, // instance default
mentionLinkShowYous: undefined, // instance default
mentionLinkBoldenYou: undefined, // instance default
hidePostStats: undefined, // instance default
hideBotIndication: undefined, // instance default
hideUserStats: undefined, // instance default
virtualScrolling: undefined, // instance default
sensitiveByDefault: undefined, // instance default
sensitiveIfSubject: undefined,
renderMisskeyMarkdown: undefined,
renderMfmOnHover: undefined, // instance default
conversationDisplay: undefined, // instance default
conversationTreeAdvanced: undefined, // instance default
conversationOtherRepliesButton: undefined, // instance default
conversationTreeFadeAncestors: undefined, // instance default
maxDepthInThread: undefined, // instance default
translationLanguage: undefined, // instance default,
postLanguage: undefined, // instance default,
supportedTranslationLanguages: {}, // instance default
userProfileDefaultTab: 'statuses',
useBlurhash: true,
}
// caching the instance default properties
export const instanceDefaultProperties = Object.entries(defaultState)
.filter(([key, value]) => value === undefined)
.map(([key, value]) => key)
function updateLocalSettings(store, settingEntries, version = null) {
if (version == null)
version = store.state.profileVersion
settingEntries.forEach(([name, value]) => {
if (store.state[name] !== value) {
store.dispatch('setOption', { name, value })
}
})
// Set this at the end to override any potentially stored profileVersion
store.commit('setOption', { name: 'profileVersion', value: version })
}
const config = {
state: { ...defaultState },
getters: {
defaultConfig (state, getters, rootState, rootGetters) {
const { instance } = rootState
return {
...defaultState,
...Object.fromEntries(
instanceDefaultProperties.map(key => [key, instance[key]])
)
}
},
mergedConfig (state, getters, rootState, rootGetters) {
const { defaultConfig } = rootGetters
return {
...defaultConfig,
// Do not override with undefined
...Object.fromEntries(Object.entries(state).filter(([k, v]) => v !== undefined))
}
}
},
mutations: {
setOption (state, { name, value }) {
state[name] = value
},
setHighlight (state, { user, color, type }) {
const data = this.state.config.highlight[user]
if (color || type) {
state.highlight[user] = { color: color || data.color, type: type || data.type }
} else {
delete state.highlight[user]
}
}
},
actions: {
syncSettings: (store) => {
store.commit('setOption', { name: 'profileVersion', value: store.state.profileVersion + 1 })
const notice = {
level: 'info',
messageKey: 'settings_profile.synchronizing',
messageArgs: { profile: store.state.profile },
timeout: 5000
}
store.dispatch('pushGlobalNotice', notice)
store.rootState.api.backendInteractor.saveSettingsProfile({
settings: store.state, profileName: store.state.profile, version: store.state.profileVersion
}).then(() => {
store.dispatch('removeGlobalNotice', notice)
store.dispatch('pushGlobalNotice', {
level: 'success',
messageKey: 'settings_profile.synchronized',
messageArgs: { profile: store.state.profile },
timeout: 2000
})
store.dispatch('listSettingsProfiles')
}).catch((err) => {
store.dispatch('removeGlobalNotice', notice)
store.dispatch('pushGlobalNotice', {
level: 'error',
messageKey: 'settings_profile.synchronization_error',
messageArgs: { error: err.message },
timeout: 5000
})
console.error(err)
})
},
deleteSettingsProfile (store, name) {
store.rootState.api.backendInteractor.deleteSettingsProfile({ profileName: name }).then(() => {
store.dispatch('listSettingsProfiles')
})
},
loadSettings (store, data) {
const knownKeys = new Set(Object.keys(defaultState))
// Limit to supported properties
const newSettingEntries =
Object.entries(data)
.filter(([key, value]) => knownKeys.has(key))
// disregard stored profileVersion; sync afterwards increases previous version
updateLocalSettings(store, newSettingEntries, null)
store.dispatch('syncSettings')
},
setHighlight ({ commit, dispatch }, { user, color, type }) {
commit('setHighlight', { user, color, type })
},
setOption ({ commit, dispatch }, { name, value, manual }) {
commit('setOption', { name, value })
if (manual === true) {
dispatch('syncSettings')
}
switch (name) {
case 'theme':
setPreset(value)
break
case 'customTheme':
case 'customThemeSource':
applyTheme(value)
break
case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value)
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value), {sameSite: 'Lax'})
dispatch('setInstanceOption', { name: 'interfaceLanguage', value })
break
case 'thirdColumnMode':
dispatch('setLayoutWidth', undefined)
break
}
},
getSettingsProfile (store, forceUpdate = false) {
const profile = store.state.profile
store.rootState.api.backendInteractor.getSettingsProfile({ store, profileName: profile })
.then(({ settings, version }) => {
console.log('found settings version', version)
if (forceUpdate || (version > store.state.profileVersion)) {
updateLocalSettings(store, Object.entries(settings), version)
} else {
console.log('settings are up to date')
}
})
.catch((err) => {
console.error(`could not fetch profile ${profile}`, err)
if (err.statusCode === 404) {
// create profile
store.dispatch('pushGlobalNotice', {
level: 'warning',
messageKey: 'settings_profile.creating',
messageArgs: { profile },
timeout: 5000
})
store.dispatch('syncSettings')
}
})
}
}
}
export default config