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:
infinite-persistence 2021-12-06 06:38:26 -08:00 committed by GitHub
parent fb7c5d0fff
commit 08ebedb4cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 28 additions and 18 deletions

View file

@ -2242,5 +2242,6 @@
"Switch to Credits": "Switch to Credits",
"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!",
"There are unsaved settings. Exit the Settings Page to finalize them.": "There are unsaved settings. Exit the Settings Page to finalize them.",
"--end--": "--end--"
}

View file

@ -1,19 +1,18 @@
import { hot } from 'react-hot-loader/root';
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 { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
import { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims';
import { selectMyChannelUrls } from 'redux/selectors/claims';
import * as SETTINGS from 'constants/settings';
import { selectMyChannelClaimIds } from 'redux/selectors/claims';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { selectClientSetting, selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
import { selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
import {
selectIsUpgradeAvailable,
selectAutoUpdateDownloaded,
selectModal,
selectActiveChannelClaim,
selectActiveChannelId,
selectIsReloadRequired,
} from 'redux/selectors/app';
import { selectUploadCount } from 'redux/selectors/publish';
@ -28,19 +27,19 @@ const select = (state) => ({
accessToken: selectAccessToken(state),
theme: selectThemePath(state),
language: selectLanguage(state),
syncEnabled: selectClientSetting(state, SETTINGS.ENABLE_SYNC),
languages: selectLoadedLanguages(state),
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state),
isReloadRequired: selectIsReloadRequired(state),
syncError: selectGetSyncErrorMessage(state),
syncIsLocked: selectSyncIsLocked(state),
uploadCount: selectUploadCount(state),
rewards: selectUnclaimedRewards(state),
isAuthenticated: selectUserVerifiedEmail(state),
currentModal: selectModal(state),
syncFatalError: selectSyncFatalError(state),
activeChannelClaim: selectActiveChannelClaim(state),
myChannelUrls: selectMyChannelUrls(state),
activeChannelId: selectActiveChannelId(state),
myChannelClaimIds: selectMyChannelClaimIds(state),
subscriptions: selectSubscriptions(state),
});

View file

@ -62,24 +62,22 @@ type Props = {
fetchCollectionListMine: () => void,
signIn: () => void,
requestDownloadUpgrade: () => void,
onSignedIn: () => void,
setLanguage: (string) => void,
isUpgradeAvailable: boolean,
isReloadRequired: boolean,
autoUpdateDownloaded: boolean,
uploadCount: number,
balance: ?number,
syncIsLocked: boolean,
syncError: ?string,
syncEnabled: boolean,
rewards: Array<Reward>,
setReferrer: (string, boolean) => void,
isAuthenticated: boolean,
socketConnect: () => void,
syncLoop: (?boolean) => void,
currentModal: any,
syncFatalError: boolean,
activeChannelClaim: ?ChannelClaim,
myChannelUrls: ?Array<string>,
activeChannelId: ?string,
myChannelClaimIds: ?Array<string>,
subscriptions: Array<Subscription>,
setActiveChannelIfNotSet: () => void,
setIncognito: (boolean) => void,
@ -103,6 +101,7 @@ function App(props: Props) {
uploadCount,
history,
syncError,
syncIsLocked,
language,
languages,
setLanguage,
@ -112,8 +111,8 @@ function App(props: Props) {
syncLoop,
currentModal,
syncFatalError,
myChannelUrls,
activeChannelClaim,
myChannelClaimIds,
activeChannelId,
setActiveChannelIfNotSet,
setIncognito,
fetchModBlockedList,
@ -150,10 +149,10 @@ function App(props: Props) {
const shouldHideNag = pathname.startsWith(`/$/${PAGES.EMBED}`) || pathname.startsWith(`/$/${PAGES.AUTH_VERIFY}`);
const userId = user && user.id;
const useCustomScrollbar = !IS_MAC;
const hasMyChannels = myChannelUrls && myChannelUrls.length > 0;
const hasNoChannels = myChannelUrls && myChannelUrls.length === 0;
const hasMyChannels = myChannelClaimIds && myChannelClaimIds.length > 0;
const hasNoChannels = myChannelClaimIds && myChannelClaimIds.length === 0;
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
const hasActiveChannelClaim = activeChannelClaim !== undefined;
const hasActiveChannelClaim = activeChannelId !== undefined;
const isPersonalized = !IS_WEB || hasVerifiedEmail;
const renderFiledrop = !IS_WEB || isAuthenticated;
const isOnline = navigator.onLine;
@ -219,6 +218,17 @@ function App(props: Props) {
}
}, [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(() => {
if (!uploadCount) return;
const handleBeforeUnload = (event) => {