Files
ncd-fe/packages/pl-fe/src/stores/settings.ts
nicole mikołajczyk 9f98b5b07d nicolium: oxlint and oxfmt migration, remove eslint
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2026-02-15 13:30:55 +01:00

285 lines
9.3 KiB
TypeScript

import isEqual from 'lodash/isEqual';
import { defineMessages } from 'react-intl';
import * as v from 'valibot';
import { create } from 'zustand';
import { mutative } from 'zustand-mutative';
import { settingsSchema, type Settings } from '@/schemas/pl-fe/settings';
import KVStore from '@/storage/kv-store';
import toast from '@/toast';
import {
KVStoreRedirectServicesItem,
resetRules,
setManualRedirectServices,
updateRedirectServicesFromUrl,
updateRulesFromUrl,
} from '@/utils/url-purify';
import type { Emoji } from '@/features/emoji';
import type { store } from '@/store';
import type { APIEntity } from '@/types/entities';
let lazyStore: typeof store;
import('@/store').then(({ store }) => (lazyStore = store)).catch(() => {});
const messages = defineMessages({
rulesUpdateSuccess: {
id: 'url_privacy.update.success',
defaultMessage: 'Successfully updated rules database',
},
rulesUpdateFail: {
id: 'url_privacy.update.fail',
defaultMessage: 'Failed to update rules database URL',
},
redirectServicesUpdateSuccess: {
id: 'url_privacy.redirect_services_update.success',
defaultMessage: 'Successfully updated redirect services',
},
redirectServicesUpdateFail: {
id: 'url_privacy.redirect_services_update.fail',
defaultMessage: 'Failed to update redirect services URL',
},
});
const settingsSchemaPartial = v.partial(settingsSchema);
type State = {
defaultSettings: Settings;
userSettings: Partial<Settings>;
settings: Settings;
actions: {
loadDefaultSettings: (settings: APIEntity) => void;
loadUserSettings: (settings: APIEntity) => void;
userSettingsSaving: () => void;
changeSetting: (path: string[], value: any) => void;
rememberEmojiUse: (emoji: Emoji) => void;
rememberLanguageUse: (language: string) => void;
};
};
const changeSetting = (object: APIEntity, path: string[], value: any, root?: Settings) => {
if (path.length === 1) {
object[path[0]] = value;
return;
}
if (typeof object[path[0]] !== 'object') {
const value = (root && (root[path[0] as keyof Settings] as APIEntity)) ?? {};
object[path[0]] = value;
}
changeSetting(object[path[0]], path.slice(1), value);
};
const mergeSettings = (state: State, updating = false) => {
const mergedSettings = { ...state.defaultSettings, ...state.userSettings };
if (updating) {
const me = lazyStore?.getState().me;
if (me) {
if (
mergedSettings.urlPrivacy.rulesUrl &&
state.settings.urlPrivacy.rulesUrl !== mergedSettings.urlPrivacy.rulesUrl
) {
updateRulesFromUrl(
me,
mergedSettings.urlPrivacy.rulesUrl,
mergedSettings.urlPrivacy.hashUrl,
)
.then(() => {
toast.success(messages.rulesUpdateSuccess);
})
.catch(() => {
toast.error(messages.rulesUpdateFail);
});
} else if (
!mergedSettings.urlPrivacy.rulesUrl &&
state.settings.urlPrivacy.rulesUrl !== mergedSettings.urlPrivacy.rulesUrl
) {
resetRules(me)
.then(() => {
toast.success(messages.rulesUpdateSuccess);
})
.catch(() => {
toast.error(messages.rulesUpdateFail);
});
}
if (
mergedSettings.urlPrivacy.redirectLinksMode === 'auto' &&
mergedSettings.urlPrivacy.redirectServicesUrl &&
state.settings.urlPrivacy.redirectServicesUrl !==
mergedSettings.urlPrivacy.redirectServicesUrl
) {
updateRedirectServicesFromUrl(me, mergedSettings.urlPrivacy.redirectServicesUrl)
.then(() => {
toast.success(messages.redirectServicesUpdateSuccess);
})
.catch(() => {
toast.error(messages.redirectServicesUpdateFail);
});
} else if (
mergedSettings.urlPrivacy.redirectLinksMode === 'manual' &&
!isEqual(
state.settings.urlPrivacy.redirectServices,
mergedSettings.urlPrivacy.redirectServices,
)
) {
setManualRedirectServices(me, mergedSettings.urlPrivacy.redirectServices)
.then(() => {
toast.success(messages.redirectServicesUpdateSuccess);
})
.catch(() => {
toast.error(messages.redirectServicesUpdateFail);
});
}
}
}
state.settings = mergedSettings;
};
const useSettingsStore = create<State>()(
mutative(
(set) => ({
defaultSettings: v.parse(settingsSchema, { locale: navigator.language }),
userSettings: {},
settings: v.parse(settingsSchema, { locale: navigator.language }),
actions: {
loadDefaultSettings: (settings: APIEntity) => {
set((state: State) => {
if (typeof settings !== 'object') return;
state.defaultSettings = v.parse(settingsSchema, settings);
mergeSettings(state);
});
},
loadUserSettings: (settings?: APIEntity) => {
set((state: State) => {
if (typeof settings !== 'object') return;
state.userSettings = v.parse(settingsSchemaPartial, settings);
const me = lazyStore?.getState().me;
if (me) {
KVStore.getItem<string>('url-purify-rules:last')
.then((value) => {
if (value !== me) {
if (state.userSettings.urlPrivacy?.rulesUrl) {
updateRulesFromUrl(
me,
state.userSettings.urlPrivacy.rulesUrl,
state.userSettings.urlPrivacy.hashUrl,
)
.then(() => {
toast.success(messages.rulesUpdateSuccess);
})
.catch(() => {
toast.error(messages.rulesUpdateFail);
});
} else {
resetRules(me);
}
switch (state.userSettings.urlPrivacy?.redirectLinksMode) {
case 'auto':
updateRedirectServicesFromUrl(
me,
state.userSettings.urlPrivacy?.redirectServicesUrl,
);
break;
case 'manual':
setManualRedirectServices(
me,
state.userSettings.urlPrivacy.redirectServices,
);
break;
default:
setManualRedirectServices(me, {});
break;
}
} else {
KVStore.getItem<KVStoreRedirectServicesItem>(
`url-purify-redirect-services:${me}`,
)
.then((services) => {
if (state.userSettings.urlPrivacy?.redirectLinksMode === 'auto') {
if (
services?.redirectServicesUrl !==
state.userSettings.urlPrivacy?.redirectServicesUrl
) {
updateRedirectServicesFromUrl(
me,
state.userSettings.urlPrivacy?.redirectServicesUrl,
);
}
} else {
setManualRedirectServices(
me,
state.userSettings.urlPrivacy?.redirectServices ?? {},
);
}
})
.catch(() => {});
}
})
.catch(() => {});
}
mergeSettings(state);
});
},
userSettingsSaving: () => {
set((state: State) => {
state.userSettings.saved = true;
mergeSettings(state);
});
},
changeSetting: (path: string[], value: any) => {
set((state: State) => {
state.userSettings.saved = false;
changeSetting(state.userSettings, path, value, state.defaultSettings);
mergeSettings(state, true);
});
},
rememberEmojiUse: (emoji: Emoji) => {
set((state: State) => {
const settings = state.userSettings;
settings.frequentlyUsedEmojis ??= {};
settings.frequentlyUsedEmojis[emoji.id] =
(settings.frequentlyUsedEmojis[emoji.id] || 0) + 1;
settings.saved = false;
mergeSettings(state);
});
},
rememberLanguageUse: (language: string) => {
set((state: State) => {
const settings = state.userSettings;
settings.frequentlyUsedLanguages ??= {};
settings.frequentlyUsedLanguages[language] =
(settings.frequentlyUsedLanguages[language] || 0) + 1;
settings.saved = false;
mergeSettings(state);
});
},
},
}),
{ enableAutoFreeze: true },
),
);
const useSettings = () => useSettingsStore((state) => state.settings);
const useSettingsStoreActions = () => useSettingsStore((state) => state.actions);
export { useSettingsStore, useSettings, useSettingsStoreActions };