2020-10-02 15:10:00 +00:00
|
|
|
import {localStorageObs} from 'app/client/lib/localStorageObs';
|
|
|
|
import {AppModel} from 'app/client/models/AppModel';
|
2022-01-14 02:55:55 +00:00
|
|
|
import {UserOrgPrefs, UserPrefs} from 'app/common/Prefs';
|
2020-10-02 15:10:00 +00:00
|
|
|
import {Computed, Observable} from 'grainjs';
|
2022-09-06 01:51:57 +00:00
|
|
|
import {CheckerT} from 'ts-interface-checker';
|
2020-10-02 15:10:00 +00:00
|
|
|
|
2022-01-14 02:55:55 +00:00
|
|
|
interface PrefsTypes {
|
|
|
|
userOrgPrefs: UserOrgPrefs;
|
|
|
|
userPrefs: UserPrefs;
|
2020-10-02 15:10:00 +00:00
|
|
|
}
|
|
|
|
|
2022-01-14 02:55:55 +00:00
|
|
|
function makePrefFunctions<P extends keyof PrefsTypes>(prefsTypeName: P) {
|
|
|
|
type PrefsType = PrefsTypes[P];
|
|
|
|
|
|
|
|
/**
|
2022-09-06 01:51:57 +00:00
|
|
|
* Creates an observable that returns a PrefsType, and which stores changes when set.
|
2022-01-14 02:55:55 +00:00
|
|
|
*
|
|
|
|
* For anon user, the prefs live in localStorage. Note that the observable isn't actually watching
|
|
|
|
* for changes on the server, it will only change when set.
|
|
|
|
*/
|
|
|
|
function getPrefsObs(appModel: AppModel): Observable<PrefsType> {
|
2022-09-06 01:51:57 +00:00
|
|
|
if (appModel.currentValidUser) {
|
|
|
|
const prefsObs = Observable.create<PrefsType>(null, appModel.currentOrg?.[prefsTypeName] ?? {});
|
2022-01-14 02:55:55 +00:00
|
|
|
return Computed.create(null, (use) => use(prefsObs))
|
|
|
|
.onWrite(prefs => {
|
|
|
|
prefsObs.set(prefs);
|
|
|
|
return appModel.api.updateOrg('current', {[prefsTypeName]: prefs});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const userId = appModel.currentUser?.id || 0;
|
|
|
|
const jsonPrefsObs = localStorageObs(`${prefsTypeName}:u=${userId}`);
|
|
|
|
return Computed.create(null, jsonPrefsObs, (use, p) => (p && JSON.parse(p) || {}) as PrefsType)
|
|
|
|
.onWrite(prefs => {
|
|
|
|
jsonPrefsObs.set(JSON.stringify(prefs));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an observable that returns a particular preference value from `prefsObs`, and which
|
|
|
|
* stores it when set.
|
|
|
|
*/
|
|
|
|
function getPrefObs<Name extends keyof PrefsType>(
|
2022-09-06 01:51:57 +00:00
|
|
|
prefsObs: Observable<PrefsType>,
|
|
|
|
prefName: Name,
|
|
|
|
options: {
|
|
|
|
defaultValue?: Exclude<PrefsType[Name], undefined>;
|
|
|
|
checker?: CheckerT<PrefsType[Name]>;
|
|
|
|
} = {}
|
|
|
|
): Observable<PrefsType[Name] | undefined> {
|
|
|
|
const {defaultValue, checker} = options;
|
|
|
|
return Computed.create(null, (use) => {
|
|
|
|
const prefs = use(prefsObs);
|
|
|
|
if (!(prefName in prefs)) { return defaultValue; }
|
|
|
|
|
|
|
|
const value = prefs[prefName];
|
|
|
|
if (checker) {
|
|
|
|
try {
|
|
|
|
checker.check(value);
|
|
|
|
} catch (e) {
|
|
|
|
console.error(`getPrefObs: preference ${prefName.toString()} has value of invalid type`, e);
|
|
|
|
return defaultValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}).onWrite(value => prefsObs.set({...prefsObs.get(), [prefName]: value}));
|
2022-01-14 02:55:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return {getPrefsObs, getPrefObs};
|
2020-10-02 15:10:00 +00:00
|
|
|
}
|
2022-01-14 02:55:55 +00:00
|
|
|
|
|
|
|
// Functions actually exported are:
|
2022-06-29 10:19:20 +00:00
|
|
|
// - getUserOrgPrefsObs(appModel): Observable<UserOrgPrefs>
|
|
|
|
// - getUserOrgPrefObs(userOrgPrefsObs, prefName): Observable<PrefType[prefName]>
|
|
|
|
// - getUserPrefsObs(appModel): Observable<UserPrefs>
|
|
|
|
// - getUserPrefObs(userPrefsObs, prefName): Observable<PrefType[prefName]>
|
2022-01-14 02:55:55 +00:00
|
|
|
|
|
|
|
export const {getPrefsObs: getUserOrgPrefsObs, getPrefObs: getUserOrgPrefObs} = makePrefFunctions('userOrgPrefs');
|
|
|
|
export const {getPrefsObs: getUserPrefsObs, getPrefObs: getUserPrefObs} = makePrefFunctions('userPrefs');
|
2022-03-21 03:41:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
// For preferences that store a list of items (such as seen docTours), this helper updates the
|
|
|
|
// preference to add itemId to it (e.g. to avoid auto-starting the docTour again in the future).
|
|
|
|
// prefKey is used only to log a more informative warning on error.
|
|
|
|
export function markAsSeen<T>(seenIdsObs: Observable<T[] | undefined>, itemId: T) {
|
|
|
|
const seenIds = seenIdsObs.get() || [];
|
|
|
|
try {
|
|
|
|
if (!seenIds.includes(itemId)) {
|
|
|
|
const seen = new Set(seenIds);
|
|
|
|
seen.add(itemId);
|
|
|
|
seenIdsObs.set([...seen].sort());
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// If we fail to save this preference, it's probably not worth alerting the user about,
|
|
|
|
// so just log to console.
|
|
|
|
// tslint:disable-next-line:no-console
|
|
|
|
console.warn("Failed to save preference in markAsSeen", e);
|
|
|
|
}
|
|
|
|
}
|
2023-03-28 16:11:40 +00:00
|
|
|
|
|
|
|
export function markAsUnSeen<T>(seenIdsObs: Observable<T[] | undefined>, itemId: T) {
|
|
|
|
const seenIds = seenIdsObs.get() || [];
|
|
|
|
try {
|
|
|
|
if (seenIds.includes(itemId)) {
|
|
|
|
const seen = new Set(seenIds);
|
|
|
|
seen.delete(itemId);
|
|
|
|
seenIdsObs.set([...seen].sort());
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// tslint:disable-next-line:no-console
|
|
|
|
console.warn("Failed to save preference in markAsUnSeen", e);
|
|
|
|
}
|
|
|
|
}
|