Settings Page: add warning for unsaved settings (#430)
* Settings Page: add warning for unsaved settings ## Issue When entering Settings Page, sync-loop is disable until user exist Settings Page. If browser is closed, changes will be lost. ## Change Add the usual browser-level modal popup. Note that all modern browsers have stopped supporting customized messages, but I still left the message there for clarity. Tried to use our own toast for it, but the handler locks all GUI until it is serviced. * app: remove unused props * app: use lighter selectors When all we need is to know if something exists or their count, use the ID version instead of the url/claim version to avoid the heavy transformation.
This commit is contained in:
parent
fb7c5d0fff
commit
08ebedb4cc
3 changed files with 28 additions and 18 deletions
|
@ -2242,5 +2242,6 @@
|
||||||
"Switch to Credits": "Switch to Credits",
|
"Switch to Credits": "Switch to Credits",
|
||||||
"Cookies": "Cookies",
|
"Cookies": "Cookies",
|
||||||
"Did someone invite you to use Odysee? Tell us who and you both get a reward!": "Did someone invite you to use Odysee? Tell us who and you both get a reward!",
|
"Did someone invite you to use Odysee? Tell us who and you both get a reward!": "Did someone invite you to use Odysee? Tell us who and you both get a reward!",
|
||||||
|
"There are unsaved settings. Exit the Settings Page to finalize them.": "There are unsaved settings. Exit the Settings Page to finalize them.",
|
||||||
"--end--": "--end--"
|
"--end--": "--end--"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectGetSyncErrorMessage, selectSyncFatalError } from 'redux/selectors/sync';
|
import { selectGetSyncErrorMessage, selectSyncFatalError, selectSyncIsLocked } from 'redux/selectors/sync';
|
||||||
import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
|
import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
|
||||||
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
||||||
import { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims';
|
import { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims';
|
||||||
import { selectMyChannelUrls } from 'redux/selectors/claims';
|
import { selectMyChannelClaimIds } from 'redux/selectors/claims';
|
||||||
import * as SETTINGS from 'constants/settings';
|
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
import { selectClientSetting, selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
import { selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
||||||
import {
|
import {
|
||||||
selectIsUpgradeAvailable,
|
selectIsUpgradeAvailable,
|
||||||
selectAutoUpdateDownloaded,
|
selectAutoUpdateDownloaded,
|
||||||
selectModal,
|
selectModal,
|
||||||
selectActiveChannelClaim,
|
selectActiveChannelId,
|
||||||
selectIsReloadRequired,
|
selectIsReloadRequired,
|
||||||
} from 'redux/selectors/app';
|
} from 'redux/selectors/app';
|
||||||
import { selectUploadCount } from 'redux/selectors/publish';
|
import { selectUploadCount } from 'redux/selectors/publish';
|
||||||
|
@ -28,19 +27,19 @@ const select = (state) => ({
|
||||||
accessToken: selectAccessToken(state),
|
accessToken: selectAccessToken(state),
|
||||||
theme: selectThemePath(state),
|
theme: selectThemePath(state),
|
||||||
language: selectLanguage(state),
|
language: selectLanguage(state),
|
||||||
syncEnabled: selectClientSetting(state, SETTINGS.ENABLE_SYNC),
|
|
||||||
languages: selectLoadedLanguages(state),
|
languages: selectLoadedLanguages(state),
|
||||||
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
|
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
|
||||||
isUpgradeAvailable: selectIsUpgradeAvailable(state),
|
isUpgradeAvailable: selectIsUpgradeAvailable(state),
|
||||||
isReloadRequired: selectIsReloadRequired(state),
|
isReloadRequired: selectIsReloadRequired(state),
|
||||||
syncError: selectGetSyncErrorMessage(state),
|
syncError: selectGetSyncErrorMessage(state),
|
||||||
|
syncIsLocked: selectSyncIsLocked(state),
|
||||||
uploadCount: selectUploadCount(state),
|
uploadCount: selectUploadCount(state),
|
||||||
rewards: selectUnclaimedRewards(state),
|
rewards: selectUnclaimedRewards(state),
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
currentModal: selectModal(state),
|
currentModal: selectModal(state),
|
||||||
syncFatalError: selectSyncFatalError(state),
|
syncFatalError: selectSyncFatalError(state),
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
activeChannelId: selectActiveChannelId(state),
|
||||||
myChannelUrls: selectMyChannelUrls(state),
|
myChannelClaimIds: selectMyChannelClaimIds(state),
|
||||||
subscriptions: selectSubscriptions(state),
|
subscriptions: selectSubscriptions(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -62,24 +62,22 @@ type Props = {
|
||||||
fetchCollectionListMine: () => void,
|
fetchCollectionListMine: () => void,
|
||||||
signIn: () => void,
|
signIn: () => void,
|
||||||
requestDownloadUpgrade: () => void,
|
requestDownloadUpgrade: () => void,
|
||||||
onSignedIn: () => void,
|
|
||||||
setLanguage: (string) => void,
|
setLanguage: (string) => void,
|
||||||
isUpgradeAvailable: boolean,
|
isUpgradeAvailable: boolean,
|
||||||
isReloadRequired: boolean,
|
isReloadRequired: boolean,
|
||||||
autoUpdateDownloaded: boolean,
|
autoUpdateDownloaded: boolean,
|
||||||
uploadCount: number,
|
uploadCount: number,
|
||||||
balance: ?number,
|
balance: ?number,
|
||||||
|
syncIsLocked: boolean,
|
||||||
syncError: ?string,
|
syncError: ?string,
|
||||||
syncEnabled: boolean,
|
|
||||||
rewards: Array<Reward>,
|
rewards: Array<Reward>,
|
||||||
setReferrer: (string, boolean) => void,
|
setReferrer: (string, boolean) => void,
|
||||||
isAuthenticated: boolean,
|
isAuthenticated: boolean,
|
||||||
socketConnect: () => void,
|
|
||||||
syncLoop: (?boolean) => void,
|
syncLoop: (?boolean) => void,
|
||||||
currentModal: any,
|
currentModal: any,
|
||||||
syncFatalError: boolean,
|
syncFatalError: boolean,
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelId: ?string,
|
||||||
myChannelUrls: ?Array<string>,
|
myChannelClaimIds: ?Array<string>,
|
||||||
subscriptions: Array<Subscription>,
|
subscriptions: Array<Subscription>,
|
||||||
setActiveChannelIfNotSet: () => void,
|
setActiveChannelIfNotSet: () => void,
|
||||||
setIncognito: (boolean) => void,
|
setIncognito: (boolean) => void,
|
||||||
|
@ -103,6 +101,7 @@ function App(props: Props) {
|
||||||
uploadCount,
|
uploadCount,
|
||||||
history,
|
history,
|
||||||
syncError,
|
syncError,
|
||||||
|
syncIsLocked,
|
||||||
language,
|
language,
|
||||||
languages,
|
languages,
|
||||||
setLanguage,
|
setLanguage,
|
||||||
|
@ -112,8 +111,8 @@ function App(props: Props) {
|
||||||
syncLoop,
|
syncLoop,
|
||||||
currentModal,
|
currentModal,
|
||||||
syncFatalError,
|
syncFatalError,
|
||||||
myChannelUrls,
|
myChannelClaimIds,
|
||||||
activeChannelClaim,
|
activeChannelId,
|
||||||
setActiveChannelIfNotSet,
|
setActiveChannelIfNotSet,
|
||||||
setIncognito,
|
setIncognito,
|
||||||
fetchModBlockedList,
|
fetchModBlockedList,
|
||||||
|
@ -150,10 +149,10 @@ function App(props: Props) {
|
||||||
const shouldHideNag = pathname.startsWith(`/$/${PAGES.EMBED}`) || pathname.startsWith(`/$/${PAGES.AUTH_VERIFY}`);
|
const shouldHideNag = pathname.startsWith(`/$/${PAGES.EMBED}`) || pathname.startsWith(`/$/${PAGES.AUTH_VERIFY}`);
|
||||||
const userId = user && user.id;
|
const userId = user && user.id;
|
||||||
const useCustomScrollbar = !IS_MAC;
|
const useCustomScrollbar = !IS_MAC;
|
||||||
const hasMyChannels = myChannelUrls && myChannelUrls.length > 0;
|
const hasMyChannels = myChannelClaimIds && myChannelClaimIds.length > 0;
|
||||||
const hasNoChannels = myChannelUrls && myChannelUrls.length === 0;
|
const hasNoChannels = myChannelClaimIds && myChannelClaimIds.length === 0;
|
||||||
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
|
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
|
||||||
const hasActiveChannelClaim = activeChannelClaim !== undefined;
|
const hasActiveChannelClaim = activeChannelId !== undefined;
|
||||||
const isPersonalized = !IS_WEB || hasVerifiedEmail;
|
const isPersonalized = !IS_WEB || hasVerifiedEmail;
|
||||||
const renderFiledrop = !IS_WEB || isAuthenticated;
|
const renderFiledrop = !IS_WEB || isAuthenticated;
|
||||||
const isOnline = navigator.onLine;
|
const isOnline = navigator.onLine;
|
||||||
|
@ -219,6 +218,17 @@ function App(props: Props) {
|
||||||
}
|
}
|
||||||
}, [userId]);
|
}, [userId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (syncIsLocked) {
|
||||||
|
const handleBeforeUnload = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.returnValue = __('There are unsaved settings. Exit the Settings Page to finalize them.');
|
||||||
|
};
|
||||||
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
}
|
||||||
|
}, [syncIsLocked]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!uploadCount) return;
|
if (!uploadCount) return;
|
||||||
const handleBeforeUnload = (event) => {
|
const handleBeforeUnload = (event) => {
|
||||||
|
|
Loading…
Reference in a new issue