@@ -6,45 +6,105 @@ import type {
66 SetSingleSettingParams ,
77 Setting ,
88} from '../../../types/api/settings' ;
9- import type { AppDispatch } from '../../defaultStore' ;
9+ import type { AppDispatch , RootState } from '../../defaultStore' ;
1010import { api } from '../api' ;
1111
1212import { SETTINGS_OPTIONS } from './constants' ;
13+ import { getSettingValue , setSettingValue } from './settings' ;
14+ import {
15+ getSettingDefault ,
16+ parseSettingValue ,
17+ readSettingValueFromLS ,
18+ setSettingValueToLS ,
19+ shouldSyncSettingToLS ,
20+ stringifySettingValue ,
21+ } from './utils' ;
22+
23+ const invalidParamsError =
24+ 'Missing required parameters (name, user) or MetaSettings API is not available' ;
1325
1426export const settingsApi = api . injectEndpoints ( {
1527 endpoints : ( builder ) => ( {
16- getSingleSetting : builder . query ( {
17- queryFn : async ( { name, user} : GetSingleSettingParams , baseApi ) => {
28+ getSingleSetting : builder . query < Setting | undefined , Partial < GetSingleSettingParams > > ( {
29+ queryFn : async ( { name, user} ) => {
1830 try {
19- if ( ! window . api . metaSettings ) {
20- throw new Error ( 'MetaSettings API is not available' ) ;
31+ // In this case localStorage should be used for settings
32+ // Actual value will be loaded to store in onQueryStarted
33+ if ( ! name || ! user || ! window . api . metaSettings ) {
34+ throw new Error ( invalidParamsError ) ;
2135 }
36+
2237 const data = await window . api . metaSettings . getSingleSetting ( {
2338 name,
2439 user,
2540 // Directly access options here to avoid them in cache key
2641 preventBatching : SETTINGS_OPTIONS [ name ] ?. preventBatching ,
2742 } ) ;
2843
29- const dispatch = baseApi . dispatch as AppDispatch ;
30-
31- // Try to sync local value if there is no backend value
32- syncLocalValueToMetaIfNoData ( data , dispatch ) ;
33-
3444 return { data} ;
3545 } catch ( error ) {
3646 return { error} ;
3747 }
3848 } ,
49+ async onQueryStarted ( args , { dispatch, queryFulfilled} ) {
50+ const { name, user} = args ;
51+
52+ if ( ! name ) {
53+ return ;
54+ }
55+
56+ const shouldUseLocalSettings =
57+ ! user || ! window . api . metaSettings || shouldSyncSettingToLS ( name ) ;
58+
59+ const defaultValue = getSettingDefault ( name ) ;
60+
61+ // Preload value from LS or default to store
62+ if ( shouldUseLocalSettings ) {
63+ const savedValue = readSettingValueFromLS ( name ) ;
64+ const value = savedValue ?? defaultValue ;
65+
66+ dispatch ( setSettingValue ( name , value ) ) ;
67+ } else {
68+ dispatch ( setSettingValue ( name , defaultValue ) ) ;
69+ }
70+
71+ try {
72+ const { data} = await queryFulfilled ;
73+
74+ // Load api value to store if present
75+ // In case local storage should be used
76+ // query will finish with an error and this code will not run
77+ const parsedValue = parseSettingValue ( data ?. value ) ;
78+
79+ if ( isNil ( data ?. value ) ) {
80+ // Try to sync local value if there is no backend value
81+ syncLocalValueToMetaIfNoData ( { ...data } , dispatch ) ;
82+ } else {
83+ dispatch ( setSettingValue ( name , parsedValue ) ) ;
84+
85+ if ( shouldSyncSettingToLS ( name ) ) {
86+ setSettingValueToLS ( name , data . value ) ;
87+ }
88+ }
89+ } catch { }
90+ } ,
3991 } ) ,
4092 setSingleSetting : builder . mutation ( {
41- queryFn : async ( params : SetSingleSettingParams ) => {
93+ queryFn : async ( {
94+ name,
95+ user,
96+ value,
97+ } : Partial < Omit < SetSingleSettingParams , 'value' > > & { value : unknown } ) => {
4298 try {
43- if ( ! window . api . metaSettings ) {
44- throw new Error ( 'MetaSettings API is not available' ) ;
99+ if ( ! name || ! user || ! window . api . metaSettings ) {
100+ throw new Error ( invalidParamsError ) ;
45101 }
46102
47- const data = await window . api . metaSettings . setSingleSetting ( params ) ;
103+ const data = await window . api . metaSettings . setSingleSetting ( {
104+ name,
105+ user,
106+ value : stringifySettingValue ( value ) ,
107+ } ) ;
48108
49109 if ( data . status !== 'SUCCESS' ) {
50110 throw new Error ( 'Setting status is not SUCCESS' ) ;
@@ -55,34 +115,51 @@ export const settingsApi = api.injectEndpoints({
55115 return { error} ;
56116 }
57117 } ,
58- async onQueryStarted ( args , { dispatch, queryFulfilled} ) {
118+ async onQueryStarted ( args , { dispatch, queryFulfilled, getState } ) {
59119 const { name, user, value} = args ;
60120
61- // Optimistically update existing cache entry
62- const patchResult = dispatch (
63- settingsApi . util . updateQueryData ( 'getSingleSetting' , { name, user} , ( draft ) => {
64- return { ...draft , name, user, value} ;
65- } ) ,
66- ) ;
121+ if ( ! name ) {
122+ return ;
123+ }
124+
125+ // Extract previous value to revert to it if set is not succesfull
126+ const previousSettingValue = getSettingValue ( getState ( ) as RootState , name ) ;
127+
128+ // Optimistically update store
129+ dispatch ( setSettingValue ( name , value ) ) ;
130+
131+ // If local storage settings should be used
132+ // Update LS and do not do any further code
133+ if ( ! user || ! window . api . metaSettings ) {
134+ setSettingValueToLS ( name , value ) ;
135+ return ;
136+ }
137+
67138 try {
68139 await queryFulfilled ;
140+
141+ // If mutation is successful, we can store new value in LS
142+ if ( shouldSyncSettingToLS ( name ) ) {
143+ setSettingValueToLS ( name , value ) ;
144+ }
69145 } catch {
70- patchResult . undo ( ) ;
146+ // Set previous value to store in case of error
147+ dispatch ( setSettingValue ( name , previousSettingValue ) ) ;
71148 }
72149 } ,
73150 } ) ,
74151 getSettings : builder . query ( {
75- queryFn : async ( { name, user} : GetSettingsParams , baseApi ) => {
152+ queryFn : async ( { name, user} : Partial < GetSettingsParams > , baseApi ) => {
76153 try {
77- if ( ! window . api . metaSettings ) {
78- throw new Error ( 'MetaSettings API is not available' ) ;
154+ if ( ! window . api . metaSettings || ! name || ! user ) {
155+ throw new Error ( invalidParamsError ) ;
79156 }
80157 const data = await window . api . metaSettings . getSettings ( { name, user} ) ;
81158
82- const patches : Promise < void > [ ] = [ ] ;
159+ const patches : Promise < unknown > [ ] = [ ] ;
83160 const dispatch = baseApi . dispatch as AppDispatch ;
84161
85- // Upsert received data in getSingleSetting cache
162+ // Upsert received data in getSingleSetting cache to prevent further redundant requests
86163 name . forEach ( ( settingName ) => {
87164 const settingData = data [ settingName ] ?? { } ;
88165
@@ -98,15 +175,18 @@ export const settingsApi = api.injectEndpoints({
98175 cacheEntryParams ,
99176 newValue ,
100177 ) ,
101- ) . then ( ( ) => {
178+ ) ;
179+ if ( isNil ( settingData . value ) ) {
102180 // Try to sync local value if there is no backend value
103- // Do it after upsert if finished to ensure proper values update order
104- // 1. New entry added to cache with nil value
105- // 2. Positive entry update - local storage value replace nil in cache
106- // 3.1. Set is successful, local value in cache
107- // 3.2. Set is not successful, cache value reverted to previous nil
108181 syncLocalValueToMetaIfNoData ( settingData , dispatch ) ;
109- } ) ;
182+ } else {
183+ const parsedValue = parseSettingValue ( settingData . value ) ;
184+ dispatch ( setSettingValue ( settingName , parsedValue ) ) ;
185+
186+ if ( shouldSyncSettingToLS ( settingName ) ) {
187+ setSettingValueToLS ( settingName , settingData . value ) ;
188+ }
189+ }
110190
111191 patches . push ( patch ) ;
112192 } ) ;
@@ -124,7 +204,11 @@ export const settingsApi = api.injectEndpoints({
124204 overrideExisting : 'throw' ,
125205} ) ;
126206
127- function syncLocalValueToMetaIfNoData ( params : Setting , dispatch : AppDispatch ) {
207+ function syncLocalValueToMetaIfNoData ( params : Partial < Setting > , dispatch : AppDispatch ) {
208+ if ( ! params . name ) {
209+ return ;
210+ }
211+
128212 const localValue = localStorage . getItem ( params . name ) ;
129213
130214 if ( isNil ( params . value ) && ! isNil ( localValue ) ) {
0 commit comments