Skip to content

Commit f9b8b0b

Browse files
absidueOothecaPickle
authored andcommitted
Improve the performance of the settings store (FreeTubeApp#6499)
1 parent a3c527c commit f9b8b0b

File tree

1 file changed

+131
-148
lines changed

1 file changed

+131
-148
lines changed

src/renderer/store/modules/settings.js

Lines changed: 131 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@ import { getSystemLocale, showToast } from '../../helpers/utils'
1919
****
2020
* Introduction
2121
*
22-
* You can add a new setting in three different methods.
22+
* You can add a new setting in two different methods.
2323
*
24-
* The first two methods benefit from the auto-generation of
24+
* The first method benefits from the auto-generation of
2525
* a getter, a mutation and a few actions related to the setting.
26-
* Those two methods should be preferred whenever possible:
26+
* This method should be preferred whenever possible:
2727
* - `state`
28-
* - `stateWithSideEffects`
2928
*
3029
* The last one DOES NOT feature any kind of auto-generation and should
3130
* only be used in scenarios that don't fall under the other 2 options:
@@ -51,7 +50,7 @@ import { getSystemLocale, showToast } from '../../helpers/utils'
5150
*
5251
* 2) You want to add a more complex setting that interacts
5352
* with other parts of the app and tech stack.
54-
* -> Please consult the `state` and `stateWithSideEffects` sections.
53+
* -> Please consult the `state` and `sideEffectHandlers` sections.
5554
*
5655
* 3) You want to add a completely custom state based setting
5756
* that does not work like the usual settings.
@@ -77,32 +76,26 @@ import { getSystemLocale, showToast } from '../../helpers/utils'
7776
* and calls `setExample` with it)
7877
*
7978
***
80-
* `stateWithSideEffects`
81-
* This object contains settings that have SIDE EFFECTS.
79+
* `sideEffectHandlers`
80+
* This object contains the side-effect handlers for settings that have SIDE EFFECTS.
8281
*
83-
* Each one of these settings must specify an object
84-
* with the following properties:
85-
* - `defaultValue`
86-
* (which is the value you would put down if
87-
* you were to add the setting to the regular `state` object)
82+
* Each one of these settings must specify a handler,
83+
* which should essentially be a callback of type
84+
* `(store, value) => void` (the same as you would use for an `action`)
85+
* that deals with the side effects for that setting
8886
*
89-
* - `sideEffectsHandler`
90-
* (which should essentially be a callback of type
91-
* `(store, value) => void`
92-
* that deals with the side effects for that setting)
93-
*
94-
* NOTE: Example implementations of such settings can be found
95-
* in the `stateWithSideEffects` object in case
87+
* NOTE: Example implementations of such handlers can be found
88+
* in the `sideEffectHandlers` object in case
9689
* the explanation isn't clear enough.
9790
*
9891
* All functions auto-generated for settings in `state`
9992
* (if you haven't read the `state` section, do it now),
100-
* are also auto-generated for settings in `stateWithSideEffects`,
93+
* are also auto-generated for settings in `sideEffectHandlers,
10194
* with a few key differences (exemplified with setting 'example'):
10295
*
10396
* - an additional action is auto-generated:
10497
* - `triggerExampleSideEffects`
105-
* (triggers the `sideEffectsHandler` for that setting;
98+
* (triggers the `handler` for that setting;
10699
* you'll most likely never call this directly)
107100
*
108101
* - the behavior of `updateExample` changes a bit:
@@ -128,12 +121,6 @@ import { getSystemLocale, showToast } from '../../helpers/utils'
128121
* to evaluate if it is truly necessary
129122
* and to ensure that the implementation works as intended.
130123
*
131-
* A good example of a setting of this type would be `usingElectron`.
132-
* This setting doesn't need to be persisted in the database
133-
* and it doesn't change over time.
134-
* Therefore, it needs a getter (which we add to `customGetters`), but
135-
* has no need for a mutation or any sort of action.
136-
*
137124
****
138125
* ENDING NOTES
139126
*
@@ -153,7 +140,7 @@ import { getSystemLocale, showToast } from '../../helpers/utils'
153140
*/
154141

155142
// HELPERS
156-
const capitalize = str => str.replace(/^\w/, c => c.toUpperCase())
143+
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
157144
const defaultGetterId = settingId => 'get' + capitalize(settingId)
158145
const defaultMutationId = settingId => 'set' + capitalize(settingId)
159146
const defaultUpdaterId = settingId => 'update' + capitalize(settingId)
@@ -311,115 +298,109 @@ const state = {
311298
// If the playlist is removed quick bookmark is disabled
312299
quickBookmarkTargetPlaylistId: 'favorites',
313300
generalAutoLoadMorePaginatedItemsEnabled: false,
301+
302+
// The settings below have side effects
303+
currentLocale: 'system',
304+
defaultInvidiousInstance: '',
305+
defaultVolume: 1,
306+
uiScale: 100,
314307
}
315308

316-
const stateWithSideEffects = {
317-
currentLocale: {
318-
defaultValue: 'system',
319-
sideEffectsHandler: async function ({ dispatch }, value) {
320-
const fallbackLocale = 'en-US'
321-
322-
let targetLocale = value
323-
if (value === 'system') {
324-
const systemLocaleName = (await getSystemLocale()).replace('_', '-') // ex: en-US
325-
const systemLocaleSplit = systemLocaleName.split('-') // ex: en
326-
const targetLocaleOptions = allLocales.filter((locale) => {
327-
// filter out other languages
328-
const localeLang = locale.split('-')[0]
329-
return localeLang.includes(systemLocaleSplit[0])
330-
}).sort((aLocaleName, bLocaleName) => {
331-
const aLocale = aLocaleName.split('-') // ex: [en, US]
332-
const bLocale = bLocaleName.split('-')
333-
334-
if (aLocaleName === systemLocaleName) { // country & language match, prefer a
335-
return -1
336-
} else if (bLocaleName === systemLocaleName) { // country & language match, prefer b
337-
return 1
338-
} else if (aLocale.length === 1) { // no country code for a, prefer a
339-
return -1
340-
} else if (bLocale.length === 1) { // no country code for b, prefer b
341-
return 1
342-
} else { // a & b have different country code from system, sort alphabetically
343-
return aLocaleName.localeCompare(bLocaleName)
344-
}
345-
})
346-
347-
if (targetLocaleOptions.length > 0) {
348-
targetLocale = targetLocaleOptions[0]
349-
} else {
350-
// Go back to default value if locale is unavailable
351-
targetLocale = fallbackLocale
352-
// Translating this string isn't necessary
353-
// because the user will always see it in the default locale
354-
// (in this case, English (US))
355-
showToast(`Locale not found, defaulting to ${fallbackLocale}`)
309+
const sideEffectHandlers = {
310+
currentLocale: async ({ dispatch }, value) => {
311+
const fallbackLocale = 'en-US'
312+
313+
let targetLocale = value
314+
if (value === 'system') {
315+
const systemLocaleName = (await getSystemLocale()).replace('_', '-') // ex: en-US
316+
const systemLocaleSplit = systemLocaleName.split('-') // ex: en
317+
const targetLocaleOptions = allLocales.filter((locale) => {
318+
// filter out other languages
319+
const localeLang = locale.split('-')[0]
320+
return localeLang.includes(systemLocaleSplit[0])
321+
}).sort((aLocaleName, bLocaleName) => {
322+
const aLocale = aLocaleName.split('-') // ex: [en, US]
323+
const bLocale = bLocaleName.split('-')
324+
325+
if (aLocaleName === systemLocaleName) { // country & language match, prefer a
326+
return -1
327+
} else if (bLocaleName === systemLocaleName) { // country & language match, prefer b
328+
return 1
329+
} else if (aLocale.length === 1) { // no country code for a, prefer a
330+
return -1
331+
} else if (bLocale.length === 1) { // no country code for b, prefer b
332+
return 1
333+
} else { // a & b have different country code from system, sort alphabetically
334+
return aLocaleName.localeCompare(bLocaleName)
356335
}
357-
}
358-
359-
const loadPromises = []
360-
361-
if (targetLocale !== fallbackLocale) {
362-
// "en-US" is used as a fallback for missing strings in other locales
363-
loadPromises.push(
364-
loadLocale(fallbackLocale)
365-
)
366-
}
336+
})
367337

368-
// "es" is used as a fallback for "es-AR" and "es-MX"
369-
if (targetLocale === 'es-AR' || targetLocale === 'es-MX') {
370-
loadPromises.push(
371-
loadLocale('es')
372-
)
338+
if (targetLocaleOptions.length > 0) {
339+
targetLocale = targetLocaleOptions[0]
340+
} else {
341+
// Go back to default value if locale is unavailable
342+
targetLocale = fallbackLocale
343+
// Translating this string isn't necessary
344+
// because the user will always see it in the default locale
345+
// (in this case, English (US))
346+
showToast(`Locale not found, defaulting to ${fallbackLocale}`)
373347
}
348+
}
374349

375-
// "pt" is used as a fallback for "pt-PT" and "pt-BR"
376-
if (targetLocale === 'pt-PT' || targetLocale === 'pt-BR') {
377-
loadPromises.push(
378-
loadLocale('pt')
379-
)
380-
}
350+
const loadPromises = []
381351

352+
if (targetLocale !== fallbackLocale) {
353+
// "en-US" is used as a fallback for missing strings in other locales
382354
loadPromises.push(
383-
loadLocale(targetLocale)
355+
loadLocale(fallbackLocale)
384356
)
357+
}
385358

386-
await Promise.allSettled(loadPromises)
359+
// "es" is used as a fallback for "es-AR" and "es-MX"
360+
if (targetLocale === 'es-AR' || targetLocale === 'es-MX') {
361+
loadPromises.push(
362+
loadLocale('es')
363+
)
364+
}
387365

388-
i18n.locale = targetLocale
389-
await dispatch('getRegionData', targetLocale)
366+
// "pt" is used as a fallback for "pt-PT" and "pt-BR"
367+
if (targetLocale === 'pt-PT' || targetLocale === 'pt-BR') {
368+
loadPromises.push(
369+
loadLocale('pt')
370+
)
390371
}
372+
373+
loadPromises.push(
374+
loadLocale(targetLocale)
375+
)
376+
377+
await Promise.allSettled(loadPromises)
378+
379+
i18n.locale = targetLocale
380+
await dispatch('getRegionData', targetLocale)
391381
},
392382

393-
defaultInvidiousInstance: {
394-
defaultValue: '',
395-
sideEffectsHandler: ({ commit, rootState }, value) => {
396-
if (value !== '' && rootState.invidious.currentInvidiousInstance !== value) {
397-
commit('setCurrentInvidiousInstance', value)
398-
}
383+
defaultInvidiousInstance: ({ commit, rootState }, value) => {
384+
if (value !== '' && rootState.invidious.currentInvidiousInstance !== value) {
385+
commit('setCurrentInvidiousInstance', value)
399386
}
400387
},
401388

402-
defaultVolume: {
403-
defaultValue: 1,
404-
sideEffectsHandler: (_, value) => {
405-
sessionStorage.setItem('volume', value)
406-
value === 0 ? sessionStorage.setItem('muted', 'true') : sessionStorage.setItem('muted', 'false')
407-
sessionStorage.setItem('defaultVolume', value)
408-
}
389+
defaultVolume: (_, value) => {
390+
sessionStorage.setItem('volume', value)
391+
value === 0 ? sessionStorage.setItem('muted', 'true') : sessionStorage.setItem('muted', 'false')
392+
sessionStorage.setItem('defaultVolume', value)
409393
},
410394

411-
uiScale: {
412-
defaultValue: 100,
413-
sideEffectsHandler: (_, value) => {
414-
if (process.env.IS_ELECTRON) {
415-
const { webFrame } = require('electron')
416-
webFrame.setZoomFactor(value / 100)
417-
}
395+
uiScale: (_, value) => {
396+
if (process.env.IS_ELECTRON) {
397+
const { webFrame } = require('electron')
398+
webFrame.setZoomFactor(value / 100)
418399
}
419400
}
420401
}
421402

422-
const settingsWithSideEffects = Object.keys(stateWithSideEffects)
403+
const settingsWithSideEffects = Object.keys(sideEffectHandlers)
423404

424405
const customState = {
425406
}
@@ -430,24 +411,30 @@ const customGetters = {
430411
const customMutations = {}
431412

432413
const customActions = {
433-
grabUserSettings: async ({ commit, dispatch }) => {
414+
grabUserSettings: async ({ commit, dispatch, state }) => {
434415
try {
435-
// Assigning default settings for settings that have side effects
436-
const userSettings = Object.entries(Object.assign({},
437-
Object.fromEntries(Object.entries(stateWithSideEffects).map(([_id, { defaultValue }]) => { return [_id, defaultValue] })),
438-
Object.fromEntries((await DBSettingHandlers.find()).map(({ _id, value }) => { return [_id, value] })))
439-
)
416+
const userSettings = await DBSettingHandlers.find()
417+
418+
const mutationIds = Object.keys(mutations)
419+
420+
const alreadyTriggeredSideEffects = []
440421

441-
for (const setting of userSettings) {
442-
const [_id, value] = setting
422+
for (const { _id, value } of userSettings) {
443423
if (settingsWithSideEffects.includes(_id)) {
444424
dispatch(defaultSideEffectsTriggerId(_id), value)
425+
alreadyTriggeredSideEffects.push(_id)
445426
}
446427

447-
if (Object.keys(mutations).includes(defaultMutationId(_id))) {
428+
if (mutationIds.includes(defaultMutationId(_id))) {
448429
commit(defaultMutationId(_id), value)
449430
}
450431
}
432+
433+
for (const _id of settingsWithSideEffects) {
434+
if (!alreadyTriggeredSideEffects.includes(_id)) {
435+
dispatch(defaultSideEffectsTriggerId(_id), state[_id])
436+
}
437+
}
451438
} catch (errMessage) {
452439
console.error(errMessage)
453440
}
@@ -619,45 +606,41 @@ const getters = {}
619606
const mutations = {}
620607
const actions = {}
621608

622-
// Add settings that contain side effects to the state
623-
Object.assign(
624-
state,
625-
Object.fromEntries(
626-
Object.keys(stateWithSideEffects).map(
627-
(key) => [
628-
key,
629-
stateWithSideEffects[key].defaultValue
630-
]
631-
)
632-
)
633-
)
634-
635609
// Build default getters, mutations and actions for every setting id
636610
for (const settingId of Object.keys(state)) {
637611
const getterId = defaultGetterId(settingId)
638612
const mutationId = defaultMutationId(settingId)
639613
const updaterId = defaultUpdaterId(settingId)
640-
const triggerId = defaultSideEffectsTriggerId(settingId)
641614

642615
getters[getterId] = (state) => state[settingId]
643616
mutations[mutationId] = (state, value) => { state[settingId] = value }
644617

645-
// If setting has side effects, generate action to handle them
646618
if (settingsWithSideEffects.includes(settingId)) {
647-
actions[triggerId] = stateWithSideEffects[settingId].sideEffectsHandler
648-
}
619+
const triggerId = defaultSideEffectsTriggerId(settingId)
649620

650-
actions[updaterId] = async ({ commit, dispatch }, value) => {
651-
try {
652-
await DBSettingHandlers.upsert(settingId, value)
621+
// If setting has side effects, generate action to handle them
622+
actions[triggerId] = sideEffectHandlers[settingId]
623+
624+
actions[updaterId] = async ({ commit, dispatch }, value) => {
625+
try {
626+
await DBSettingHandlers.upsert(settingId, value)
653627

654-
if (settingsWithSideEffects.includes(settingId)) {
655628
dispatch(triggerId, value)
656-
}
657629

658-
commit(mutationId, value)
659-
} catch (errMessage) {
660-
console.error(errMessage)
630+
commit(mutationId, value)
631+
} catch (errMessage) {
632+
console.error(errMessage)
633+
}
634+
}
635+
} else {
636+
actions[updaterId] = async ({ commit }, value) => {
637+
try {
638+
await DBSettingHandlers.upsert(settingId, value)
639+
640+
commit(mutationId, value)
641+
} catch (errMessage) {
642+
console.error(errMessage)
643+
}
661644
}
662645
}
663646
}

0 commit comments

Comments
 (0)