mirror of
https://github.com/p-stream/p-stream.git
synced 2026-03-11 17:55:33 +00:00
Prevent settings from loading empty and rewriting to backend
- No saves happen until backend settings are loaded and applied - Automatic syncers wait for settings to be loaded before syncing
This commit is contained in:
parent
bd21f7d104
commit
3de8a35df4
8 changed files with 51 additions and 26 deletions
|
|
@ -193,6 +193,7 @@ export function PlaybackSettingsView({ id }: { id: string }) {
|
|||
|
||||
const account = useAuthStore((s) => s.account);
|
||||
const backendUrl = useBackendUrl();
|
||||
const settingsLoaded = usePreferencesStore((s) => s._settingsLoaded);
|
||||
const allowAutoplay = useMemo(() => isAutoplayAllowed(), []);
|
||||
const canShowAutoplay =
|
||||
!isInWatchParty && allowAutoplay && !enableLowPerformanceMode;
|
||||
|
|
@ -200,7 +201,7 @@ export function PlaybackSettingsView({ id }: { id: string }) {
|
|||
// Save settings to backend
|
||||
const saveThumbnailSetting = useCallback(
|
||||
async (value: boolean) => {
|
||||
if (!account || !backendUrl) return;
|
||||
if (!account || !backendUrl || !settingsLoaded) return;
|
||||
|
||||
try {
|
||||
await updateSettings(backendUrl, account, {
|
||||
|
|
@ -210,12 +211,12 @@ export function PlaybackSettingsView({ id }: { id: string }) {
|
|||
console.error("Failed to save thumbnail setting:", error);
|
||||
}
|
||||
},
|
||||
[account, backendUrl],
|
||||
[account, backendUrl, settingsLoaded],
|
||||
);
|
||||
|
||||
const saveAutoplaySetting = useCallback(
|
||||
async (value: boolean) => {
|
||||
if (!account || !backendUrl) return;
|
||||
if (!account || !backendUrl || !settingsLoaded) return;
|
||||
|
||||
try {
|
||||
await updateSettings(backendUrl, account, {
|
||||
|
|
@ -225,7 +226,7 @@ export function PlaybackSettingsView({ id }: { id: string }) {
|
|||
console.error("Failed to save autoplay setting:", error);
|
||||
}
|
||||
},
|
||||
[account, backendUrl],
|
||||
[account, backendUrl, settingsLoaded],
|
||||
);
|
||||
|
||||
const setPlaybackRate = useCallback(
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export function useAuthData() {
|
|||
);
|
||||
const setFebboxKey = usePreferencesStore((s) => s.setFebboxKey);
|
||||
const setRealDebridKey = usePreferencesStore((s) => s.setRealDebridKey);
|
||||
const setSettingsLoaded = usePreferencesStore((s) => s.setSettingsLoaded);
|
||||
|
||||
const replaceBookmarks = useBookmarkStore((s) => s.replaceBookmarks);
|
||||
const replaceItems = useProgressStore((s) => s.replaceItems);
|
||||
|
|
@ -109,12 +110,14 @@ export function useAuthData() {
|
|||
clearProgress();
|
||||
clearGroupOrder();
|
||||
setFebboxKey(null);
|
||||
setSettingsLoaded(false);
|
||||
}, [
|
||||
removeAccount,
|
||||
clearBookmarks,
|
||||
clearProgress,
|
||||
clearGroupOrder,
|
||||
setFebboxKey,
|
||||
setSettingsLoaded,
|
||||
]);
|
||||
|
||||
const syncData = useCallback(
|
||||
|
|
@ -215,6 +218,10 @@ export function useAuthData() {
|
|||
|
||||
if (settings.febboxKey !== undefined) {
|
||||
setFebboxKey(settings.febboxKey);
|
||||
} else {
|
||||
// Only set to null if backend explicitly returns null/undefined
|
||||
// Don't overwrite with defaults if backend doesn't have the setting
|
||||
setFebboxKey(null);
|
||||
}
|
||||
|
||||
if (settings.realDebridKey !== undefined) {
|
||||
|
|
@ -246,6 +253,9 @@ export function useAuthData() {
|
|||
if (settings.enableDoubleClickToSeek !== undefined) {
|
||||
setEnableDoubleClickToSeek(settings.enableDoubleClickToSeek);
|
||||
}
|
||||
|
||||
// Mark settings as loaded to prevent saving defaults
|
||||
setSettingsLoaded(true);
|
||||
},
|
||||
[
|
||||
replaceBookmarks,
|
||||
|
|
@ -278,6 +288,7 @@ export function useAuthData() {
|
|||
setHomeSectionOrder,
|
||||
setManualSourceSelection,
|
||||
setEnableDoubleClickToSeek,
|
||||
setSettingsLoaded,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const AUTH_CHECK_INTERVAL = 12 * 60 * 60 * 1000;
|
|||
export function useAuthRestore() {
|
||||
const { account } = useAuthStore();
|
||||
const { restore } = useAuth();
|
||||
const setSettingsLoading = useAuthStore((s) => s.setSettingsLoading);
|
||||
const hasRestored = useRef(false);
|
||||
|
||||
useInterval(() => {
|
||||
|
|
@ -17,9 +18,13 @@ export function useAuthRestore() {
|
|||
|
||||
const result = useAsync(async () => {
|
||||
if (hasRestored.current || !account) return;
|
||||
await restore(account).finally(() => {
|
||||
setSettingsLoading(true);
|
||||
try {
|
||||
await restore(account);
|
||||
} finally {
|
||||
setSettingsLoading(false);
|
||||
hasRestored.current = true;
|
||||
});
|
||||
}
|
||||
}, []); // no deps because we don't want to it ever rerun after the first time
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { usePreferencesStore } from "@/stores/preferences";
|
|||
export function useEmbedOrderState() {
|
||||
const account = useAuthStore((s) => s.account);
|
||||
const backendUrl = useBackendUrl();
|
||||
const settingsLoaded = usePreferencesStore((s) => s._settingsLoaded);
|
||||
|
||||
// Get current values from store
|
||||
const embedOrder = usePreferencesStore((s) => s.embedOrder);
|
||||
|
|
@ -51,7 +52,7 @@ export function useEmbedOrderState() {
|
|||
|
||||
// Save changes to backend and update store
|
||||
const saveChanges = useCallback(async () => {
|
||||
if (!account || !backendUrl) return;
|
||||
if (!account || !backendUrl || !settingsLoaded) return;
|
||||
|
||||
try {
|
||||
await updateSettings(backendUrl, account, {
|
||||
|
|
@ -71,6 +72,7 @@ export function useEmbedOrderState() {
|
|||
}, [
|
||||
account,
|
||||
backendUrl,
|
||||
settingsLoaded,
|
||||
localEmbedOrder,
|
||||
localEnableEmbedOrder,
|
||||
localDisabledEmbeds,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
encryptData,
|
||||
} from "@/backend/accounts/crypto";
|
||||
import { getSessions, updateSession } from "@/backend/accounts/sessions";
|
||||
import { getSettings, updateSettings } from "@/backend/accounts/settings";
|
||||
import { updateSettings } from "@/backend/accounts/settings";
|
||||
import { editUser } from "@/backend/accounts/user";
|
||||
import { getAllProviders } from "@/backend/providers/providers";
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
|
|
@ -364,6 +364,7 @@ export function SettingsPage() {
|
|||
const account = useAuthStore((s) => s.account);
|
||||
const updateProfile = useAuthStore((s) => s.setAccountProfile);
|
||||
const updateDeviceName = useAuthStore((s) => s.updateDeviceName);
|
||||
const settingsLoaded = usePreferencesStore((s) => s._settingsLoaded);
|
||||
const decryptedName = useMemo(() => {
|
||||
if (!account) return "";
|
||||
return decryptData(account.deviceName, base64ToBuffer(account.seed));
|
||||
|
|
@ -374,21 +375,6 @@ export function SettingsPage() {
|
|||
const { logout } = useAuth();
|
||||
const user = useAuthStore();
|
||||
|
||||
useEffect(() => {
|
||||
const loadSettings = async () => {
|
||||
if (account && backendUrl) {
|
||||
const settings = await getSettings(backendUrl, account);
|
||||
if (settings.febboxKey) {
|
||||
setFebboxKey(settings.febboxKey);
|
||||
}
|
||||
if (settings.realDebridKey) {
|
||||
setRealDebridKey(settings.realDebridKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
loadSettings();
|
||||
}, [account, backendUrl, setFebboxKey, setRealDebridKey]);
|
||||
|
||||
const state = useSettingsState(
|
||||
activeTheme,
|
||||
appLanguage,
|
||||
|
|
@ -459,7 +445,7 @@ export function SettingsPage() {
|
|||
);
|
||||
|
||||
const saveChanges = useCallback(async () => {
|
||||
if (account && backendUrl) {
|
||||
if (account && backendUrl && settingsLoaded) {
|
||||
if (
|
||||
state.appLanguage.changed ||
|
||||
state.theme.changed ||
|
||||
|
|
@ -570,6 +556,7 @@ export function SettingsPage() {
|
|||
}, [
|
||||
account,
|
||||
backendUrl,
|
||||
settingsLoaded,
|
||||
setEnableThumbnails,
|
||||
setFebboxKey,
|
||||
setRealDebridKey,
|
||||
|
|
@ -705,7 +692,7 @@ export function SettingsPage() {
|
|||
</SettingsLayout>
|
||||
<Transition
|
||||
animation="fade"
|
||||
show={state.changed}
|
||||
show={state.changed && settingsLoaded}
|
||||
className="bg-settings-saveBar-background border-t border-settings-card-border/50 py-4 transition-opacity w-full fixed bottom-0 flex justify-between flex-col md:flex-row px-8 items-start md:items-center gap-3 z-[999]"
|
||||
>
|
||||
<p className="text-type-danger">{t("settings.unsaved")}</p>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ interface AuthStore {
|
|||
account: null | AccountWithToken;
|
||||
backendUrl: null | string;
|
||||
proxySet: null | string[];
|
||||
settingsLoading: boolean;
|
||||
removeAccount(): void;
|
||||
setAccount(acc: AccountWithToken): void;
|
||||
updateDeviceName(deviceName: string): void;
|
||||
|
|
@ -29,6 +30,7 @@ interface AuthStore {
|
|||
setAccountProfile(acc: Account["profile"]): void;
|
||||
setBackendUrl(url: null | string): void;
|
||||
setProxySet(urls: null | string[]): void;
|
||||
setSettingsLoading(loading: boolean): void;
|
||||
}
|
||||
|
||||
export const useAuthStore = create(
|
||||
|
|
@ -37,6 +39,7 @@ export const useAuthStore = create(
|
|||
account: null,
|
||||
backendUrl: null,
|
||||
proxySet: null,
|
||||
settingsLoading: false,
|
||||
setAccount(acc) {
|
||||
set((s) => {
|
||||
s.account = acc;
|
||||
|
|
@ -57,6 +60,11 @@ export const useAuthStore = create(
|
|||
s.proxySet = urls;
|
||||
});
|
||||
},
|
||||
setSettingsLoading(loading) {
|
||||
set((s) => {
|
||||
s.settingsLoading = loading;
|
||||
});
|
||||
},
|
||||
setAccountProfile(profile) {
|
||||
set((s) => {
|
||||
if (s.account) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export interface PreferencesStore {
|
|||
homeSectionOrder: string[];
|
||||
manualSourceSelection: boolean;
|
||||
enableDoubleClickToSeek: boolean;
|
||||
_settingsLoaded: boolean;
|
||||
|
||||
setEnableThumbnails(v: boolean): void;
|
||||
setEnableAutoplay(v: boolean): void;
|
||||
|
|
@ -52,6 +53,7 @@ export interface PreferencesStore {
|
|||
setHomeSectionOrder(v: string[]): void;
|
||||
setManualSourceSelection(v: boolean): void;
|
||||
setEnableDoubleClickToSeek(v: boolean): void;
|
||||
setSettingsLoaded(loaded: boolean): void;
|
||||
}
|
||||
|
||||
export const usePreferencesStore = create(
|
||||
|
|
@ -75,6 +77,7 @@ export const usePreferencesStore = create(
|
|||
proxyTmdb: false,
|
||||
febboxKey: null,
|
||||
realDebridKey: null,
|
||||
_settingsLoaded: false,
|
||||
enableLowPerformanceMode: false,
|
||||
enableNativeSubtitles: false,
|
||||
enableHoldToBoost: true,
|
||||
|
|
@ -206,6 +209,11 @@ export const usePreferencesStore = create(
|
|||
s.enableDoubleClickToSeek = v;
|
||||
});
|
||||
},
|
||||
setSettingsLoaded(loaded) {
|
||||
set((s) => {
|
||||
s._settingsLoaded = loaded;
|
||||
});
|
||||
},
|
||||
})),
|
||||
{
|
||||
name: "__MW::preferences",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { updateSettings } from "@/backend/accounts/settings";
|
|||
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useSubtitleStore } from "@/stores/subtitles";
|
||||
import { usePreferencesStore } from "@/stores/preferences";
|
||||
|
||||
const syncIntervalMs = 5 * 1000;
|
||||
|
||||
|
|
@ -12,11 +13,13 @@ export function SettingsSyncer() {
|
|||
(s) => s.importSubtitleLanguage,
|
||||
);
|
||||
const url = useBackendUrl();
|
||||
const settingsLoading = useAuthStore((s) => s.settingsLoading);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
(async () => {
|
||||
if (!url) return;
|
||||
if (settingsLoading) return; // Don't sync while settings are loading from backend
|
||||
const state = useSubtitleStore.getState();
|
||||
const user = useAuthStore.getState();
|
||||
if (state.lastSync.lastSelectedLanguage === state.lastSelectedLanguage)
|
||||
|
|
@ -33,7 +36,7 @@ export function SettingsSyncer() {
|
|||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [importSubtitleLanguage, url]);
|
||||
}, [importSubtitleLanguage, url, settingsLoading]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue