[Maps] fix notifying user about losing unsaved changes when navigating away from map (#72003)
* [Maps] fix notifying user about losing unsaved changes when navigating away from map * clean up * tslint fixes Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
394e7ba8ba
commit
46eba14669
|
@ -17,18 +17,18 @@ import { LoadMapAndRender } from './routes/maps_app/load_map_and_render';
|
||||||
export let goToSpecifiedPath;
|
export let goToSpecifiedPath;
|
||||||
export let kbnUrlStateStorage;
|
export let kbnUrlStateStorage;
|
||||||
|
|
||||||
export async function renderApp(context, { appBasePath, element, history }) {
|
export async function renderApp(context, { appBasePath, element, history, onAppLeave }) {
|
||||||
goToSpecifiedPath = (path) => history.push(path);
|
goToSpecifiedPath = (path) => history.push(path);
|
||||||
kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, history });
|
kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, history });
|
||||||
|
|
||||||
render(<App history={history} appBasePath={appBasePath} />, element);
|
render(<App history={history} appBasePath={appBasePath} onAppLeave={onAppLeave} />, element);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unmountComponentAtNode(element);
|
unmountComponentAtNode(element);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const App = ({ history, appBasePath }) => {
|
const App = ({ history, appBasePath, onAppLeave }) => {
|
||||||
const store = getStore();
|
const store = getStore();
|
||||||
const I18nContext = getCoreI18n().Context;
|
const I18nContext = getCoreI18n().Context;
|
||||||
|
|
||||||
|
@ -37,8 +37,20 @@ const App = ({ history, appBasePath }) => {
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Router basename={appBasePath} history={history}>
|
<Router basename={appBasePath} history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={`/map/:savedMapId`} component={LoadMapAndRender} />
|
<Route
|
||||||
<Route exact path={`/map`} component={LoadMapAndRender} />
|
path={`/map/:savedMapId`}
|
||||||
|
render={(props) => (
|
||||||
|
<LoadMapAndRender
|
||||||
|
savedMapId={props.match.params.savedMapId}
|
||||||
|
onAppLeave={onAppLeave}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`/map`}
|
||||||
|
render={() => <LoadMapAndRender onAppLeave={onAppLeave} />}
|
||||||
|
/>
|
||||||
// Redirect other routes to list, or if hash-containing, their non-hash equivalents
|
// Redirect other routes to list, or if hash-containing, their non-hash equivalents
|
||||||
<Route
|
<Route
|
||||||
path={``}
|
path={``}
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { getCoreChrome } from '../../kibana_services';
|
|
||||||
import { MAP_PATH } from '../../../common/constants';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { getLayerListRaw } from '../../selectors/map_selectors';
|
|
||||||
import { copyPersistentState } from '../../reducers/util';
|
|
||||||
import { getStore } from '../store_operations';
|
|
||||||
import { goToSpecifiedPath } from '../maps_router';
|
|
||||||
|
|
||||||
function hasUnsavedChanges(savedMap, initialLayerListConfig) {
|
|
||||||
const state = getStore().getState();
|
|
||||||
const layerList = getLayerListRaw(state);
|
|
||||||
const layerListConfigOnly = copyPersistentState(layerList);
|
|
||||||
|
|
||||||
const savedLayerList = savedMap.getLayerList();
|
|
||||||
|
|
||||||
return !savedLayerList
|
|
||||||
? !_.isEqual(layerListConfigOnly, initialLayerListConfig)
|
|
||||||
: // savedMap stores layerList as a JSON string using JSON.stringify.
|
|
||||||
// JSON.stringify removes undefined properties from objects.
|
|
||||||
// savedMap.getLayerList converts the JSON string back into Javascript array of objects.
|
|
||||||
// Need to perform the same process for layerListConfigOnly to compare apples to apples
|
|
||||||
// and avoid undefined properties in layerListConfigOnly triggering unsaved changes.
|
|
||||||
!_.isEqual(JSON.parse(JSON.stringify(layerListConfigOnly)), savedLayerList);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateBreadcrumbs = (savedMap, initialLayerListConfig, currentPath = '') => {
|
|
||||||
const isOnMapNow = currentPath.startsWith(`/${MAP_PATH}`);
|
|
||||||
const breadCrumbs = isOnMapNow
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
text: i18n.translate('xpack.maps.mapController.mapsBreadcrumbLabel', {
|
|
||||||
defaultMessage: 'Maps',
|
|
||||||
}),
|
|
||||||
onClick: () => {
|
|
||||||
if (hasUnsavedChanges(savedMap, initialLayerListConfig)) {
|
|
||||||
const navigateAway = window.confirm(
|
|
||||||
i18n.translate('xpack.maps.breadCrumbs.unsavedChangesWarning', {
|
|
||||||
defaultMessage: `Your unsaved changes might not be saved`,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
if (navigateAway) {
|
|
||||||
goToSpecifiedPath('/');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
goToSpecifiedPath('/');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ text: savedMap.title },
|
|
||||||
]
|
|
||||||
: [];
|
|
||||||
getCoreChrome().setBreadcrumbs(breadCrumbs);
|
|
||||||
};
|
|
|
@ -21,7 +21,6 @@ import {
|
||||||
showSaveModal,
|
showSaveModal,
|
||||||
} from '../../../../../../../src/plugins/saved_objects/public';
|
} from '../../../../../../../src/plugins/saved_objects/public';
|
||||||
import { MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants';
|
import { MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants';
|
||||||
import { updateBreadcrumbs } from '../breadcrumbs';
|
|
||||||
import { goToSpecifiedPath } from '../../maps_router';
|
import { goToSpecifiedPath } from '../../maps_router';
|
||||||
|
|
||||||
export function MapsTopNavMenu({
|
export function MapsTopNavMenu({
|
||||||
|
@ -35,7 +34,6 @@ export function MapsTopNavMenu({
|
||||||
refreshConfig,
|
refreshConfig,
|
||||||
setRefreshConfig,
|
setRefreshConfig,
|
||||||
setRefreshStoreConfig,
|
setRefreshStoreConfig,
|
||||||
initialLayerListConfig,
|
|
||||||
indexPatterns,
|
indexPatterns,
|
||||||
updateFiltersAndDispatch,
|
updateFiltersAndDispatch,
|
||||||
isSaveDisabled,
|
isSaveDisabled,
|
||||||
|
@ -44,7 +42,7 @@ export function MapsTopNavMenu({
|
||||||
openMapSettings,
|
openMapSettings,
|
||||||
inspectorAdapters,
|
inspectorAdapters,
|
||||||
syncAppAndGlobalState,
|
syncAppAndGlobalState,
|
||||||
currentPath,
|
setBreadcrumbs,
|
||||||
isOpenSettingsDisabled,
|
isOpenSettingsDisabled,
|
||||||
}) {
|
}) {
|
||||||
const { TopNavMenu } = getNavigation().ui;
|
const { TopNavMenu } = getNavigation().ui;
|
||||||
|
@ -64,14 +62,13 @@ export function MapsTopNavMenu({
|
||||||
// Nav settings
|
// Nav settings
|
||||||
const config = getTopNavConfig(
|
const config = getTopNavConfig(
|
||||||
savedMap,
|
savedMap,
|
||||||
initialLayerListConfig,
|
|
||||||
isOpenSettingsDisabled,
|
isOpenSettingsDisabled,
|
||||||
isSaveDisabled,
|
isSaveDisabled,
|
||||||
closeFlyout,
|
closeFlyout,
|
||||||
enableFullScreen,
|
enableFullScreen,
|
||||||
openMapSettings,
|
openMapSettings,
|
||||||
inspectorAdapters,
|
inspectorAdapters,
|
||||||
currentPath
|
setBreadcrumbs
|
||||||
);
|
);
|
||||||
|
|
||||||
const submitQuery = function ({ dateRange, query }) {
|
const submitQuery = function ({ dateRange, query }) {
|
||||||
|
@ -121,14 +118,13 @@ export function MapsTopNavMenu({
|
||||||
|
|
||||||
function getTopNavConfig(
|
function getTopNavConfig(
|
||||||
savedMap,
|
savedMap,
|
||||||
initialLayerListConfig,
|
|
||||||
isOpenSettingsDisabled,
|
isOpenSettingsDisabled,
|
||||||
isSaveDisabled,
|
isSaveDisabled,
|
||||||
closeFlyout,
|
closeFlyout,
|
||||||
enableFullScreen,
|
enableFullScreen,
|
||||||
openMapSettings,
|
openMapSettings,
|
||||||
inspectorAdapters,
|
inspectorAdapters,
|
||||||
currentPath
|
setBreadcrumbs
|
||||||
) {
|
) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -210,19 +206,15 @@ function getTopNavConfig(
|
||||||
isTitleDuplicateConfirmed,
|
isTitleDuplicateConfirmed,
|
||||||
onTitleDuplicate,
|
onTitleDuplicate,
|
||||||
};
|
};
|
||||||
return doSave(
|
return doSave(savedMap, saveOptions, closeFlyout, setBreadcrumbs).then(
|
||||||
savedMap,
|
(response) => {
|
||||||
saveOptions,
|
// If the save wasn't successful, put the original values back.
|
||||||
initialLayerListConfig,
|
if (!response.id || response.error) {
|
||||||
closeFlyout,
|
savedMap.title = currentTitle;
|
||||||
currentPath
|
}
|
||||||
).then((response) => {
|
return response;
|
||||||
// If the save wasn't successful, put the original values back.
|
|
||||||
if (!response.id || response.error) {
|
|
||||||
savedMap.title = currentTitle;
|
|
||||||
}
|
}
|
||||||
return response;
|
);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveModal = (
|
const saveModal = (
|
||||||
|
@ -243,7 +235,7 @@ function getTopNavConfig(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doSave(savedMap, saveOptions, initialLayerListConfig, closeFlyout, currentPath) {
|
async function doSave(savedMap, saveOptions, closeFlyout, setBreadcrumbs) {
|
||||||
closeFlyout();
|
closeFlyout();
|
||||||
savedMap.syncWithStore();
|
savedMap.syncWithStore();
|
||||||
let id;
|
let id;
|
||||||
|
@ -265,7 +257,7 @@ async function doSave(savedMap, saveOptions, initialLayerListConfig, closeFlyout
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
goToSpecifiedPath(`/map/${id}${window.location.hash}`);
|
goToSpecifiedPath(`/map/${id}${window.location.hash}`);
|
||||||
updateBreadcrumbs(savedMap, initialLayerListConfig, currentPath);
|
setBreadcrumbs();
|
||||||
|
|
||||||
getToasts().addSuccess({
|
getToasts().addSuccess({
|
||||||
title: i18n.translate('xpack.maps.mapController.saveSuccessMessage', {
|
title: i18n.translate('xpack.maps.mapController.saveSuccessMessage', {
|
||||||
|
|
|
@ -33,7 +33,6 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { addHelpMenuToAppChrome } from '../../../help_menu_util';
|
import { addHelpMenuToAppChrome } from '../../../help_menu_util';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { updateBreadcrumbs } from '../../page_elements/breadcrumbs';
|
|
||||||
import { goToSpecifiedPath } from '../../maps_router';
|
import { goToSpecifiedPath } from '../../maps_router';
|
||||||
|
|
||||||
export const EMPTY_FILTER = '';
|
export const EMPTY_FILTER = '';
|
||||||
|
@ -53,17 +52,13 @@ export class MapsListView extends React.Component {
|
||||||
listingLimit: getUiSettings().get('savedObjects:listingLimit'),
|
listingLimit: getUiSettings().get('savedObjects:listingLimit'),
|
||||||
};
|
};
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
|
||||||
this._isMounted = true;
|
|
||||||
updateBreadcrumbs();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._isMounted = false;
|
this._isMounted = false;
|
||||||
this.debouncedFetch.cancel();
|
this.debouncedFetch.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this._isMounted = true;
|
||||||
this.initMapList();
|
this.initMapList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
getFilters,
|
getFilters,
|
||||||
getQueryableUniqueIndexPatternIds,
|
getQueryableUniqueIndexPatternIds,
|
||||||
getRefreshConfig,
|
getRefreshConfig,
|
||||||
|
hasUnsavedChanges,
|
||||||
} from '../../../selectors/map_selectors';
|
} from '../../../selectors/map_selectors';
|
||||||
import {
|
import {
|
||||||
replaceLayerList,
|
replaceLayerList,
|
||||||
|
@ -34,6 +35,9 @@ function mapStateToProps(state = {}) {
|
||||||
flyoutDisplay: getFlyoutDisplay(state),
|
flyoutDisplay: getFlyoutDisplay(state),
|
||||||
refreshConfig: getRefreshConfig(state),
|
refreshConfig: getRefreshConfig(state),
|
||||||
filters: getFilters(state),
|
filters: getFilters(state),
|
||||||
|
hasUnsavedChanges: (savedMap, initialLayerListConfig) => {
|
||||||
|
return hasUnsavedChanges(state, savedMap, initialLayerListConfig);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,8 @@ export const LoadMapAndRender = class extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _loadSavedMap() {
|
async _loadSavedMap() {
|
||||||
const { savedMapId } = this.props.match.params;
|
|
||||||
try {
|
try {
|
||||||
const savedMap = await getMapsSavedObjectLoader().get(savedMapId);
|
const savedMap = await getMapsSavedObjectLoader().get(this.props.savedMapId);
|
||||||
if (this._isMounted) {
|
if (this._isMounted) {
|
||||||
this.setState({ savedMap });
|
this.setState({ savedMap });
|
||||||
}
|
}
|
||||||
|
@ -48,11 +47,11 @@ export const LoadMapAndRender = class extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { savedMap, failedToLoad } = this.state;
|
const { savedMap, failedToLoad } = this.state;
|
||||||
|
|
||||||
if (failedToLoad) {
|
if (failedToLoad) {
|
||||||
return <Redirect to="/" />;
|
return <Redirect to="/" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentPath = this.props.match.url;
|
return savedMap ? <MapsAppView savedMap={savedMap} onAppLeave={this.props.onAppLeave} /> : null;
|
||||||
return savedMap ? <MapsAppView savedMap={savedMap} currentPath={currentPath} /> : null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,12 +30,15 @@ import {
|
||||||
} from '../../state_syncing/global_sync';
|
} from '../../state_syncing/global_sync';
|
||||||
import { AppStateManager } from '../../state_syncing/app_state_manager';
|
import { AppStateManager } from '../../state_syncing/app_state_manager';
|
||||||
import { useAppStateSyncing } from '../../state_syncing/app_sync';
|
import { useAppStateSyncing } from '../../state_syncing/app_sync';
|
||||||
import { updateBreadcrumbs } from '../../page_elements/breadcrumbs';
|
|
||||||
import { esFilters } from '../../../../../../../src/plugins/data/public';
|
import { esFilters } from '../../../../../../../src/plugins/data/public';
|
||||||
import { GisMap } from '../../../connected_components/gis_map';
|
import { GisMap } from '../../../connected_components/gis_map';
|
||||||
|
import { goToSpecifiedPath } from '../../maps_router';
|
||||||
|
|
||||||
|
const unsavedChangesWarning = i18n.translate('xpack.maps.breadCrumbs.unsavedChangesWarning', {
|
||||||
|
defaultMessage: 'Your map has unsaved changes. Are you sure you want to leave?',
|
||||||
|
});
|
||||||
|
|
||||||
export class MapsAppView extends React.Component {
|
export class MapsAppView extends React.Component {
|
||||||
_visibleSubscription = null;
|
|
||||||
_globalSyncUnsubscribe = null;
|
_globalSyncUnsubscribe = null;
|
||||||
_globalSyncChangeMonitorSubscription = null;
|
_globalSyncChangeMonitorSubscription = null;
|
||||||
_appSyncUnsubscribe = null;
|
_appSyncUnsubscribe = null;
|
||||||
|
@ -47,16 +50,13 @@ export class MapsAppView extends React.Component {
|
||||||
indexPatterns: [],
|
indexPatterns: [],
|
||||||
prevIndexPatternIds: [],
|
prevIndexPatternIds: [],
|
||||||
initialized: false,
|
initialized: false,
|
||||||
isVisible: true,
|
|
||||||
savedQuery: '',
|
savedQuery: '',
|
||||||
currentPath: '',
|
|
||||||
initialLayerListConfig: null,
|
initialLayerListConfig: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { savedMap, currentPath } = this.props;
|
const { savedMap } = this.props;
|
||||||
this.setState({ currentPath });
|
|
||||||
|
|
||||||
getCoreChrome().docTitle.change(savedMap.title);
|
getCoreChrome().docTitle.change(savedMap.title);
|
||||||
getCoreChrome().recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, savedMap.id);
|
getCoreChrome().recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, savedMap.id);
|
||||||
|
@ -77,29 +77,72 @@ export class MapsAppView extends React.Component {
|
||||||
this._updateStateFromSavedQuery(initAppState.savedQuery);
|
this._updateStateFromSavedQuery(initAppState.savedQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Monitor visibility
|
|
||||||
this._visibleSubscription = getCoreChrome()
|
|
||||||
.getIsVisible$()
|
|
||||||
.subscribe((isVisible) => this.setState({ isVisible }));
|
|
||||||
this._initMap();
|
this._initMap();
|
||||||
|
|
||||||
|
this._setBreadcrumbs();
|
||||||
|
|
||||||
|
this.props.onAppLeave((actions) => {
|
||||||
|
if (this._hasUnsavedChanges()) {
|
||||||
|
if (!window.confirm(unsavedChangesWarning)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actions.default();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_initBreadcrumbUpdater = () => {
|
componentDidUpdate() {
|
||||||
const { initialLayerListConfig, currentPath } = this.state;
|
|
||||||
updateBreadcrumbs(this.props.savedMap, initialLayerListConfig, currentPath);
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
const { currentPath: prevCurrentPath } = prevState;
|
|
||||||
const { currentPath, initialLayerListConfig } = this.state;
|
|
||||||
const { savedMap } = this.props;
|
|
||||||
if (savedMap && initialLayerListConfig && currentPath !== prevCurrentPath) {
|
|
||||||
updateBreadcrumbs(savedMap, initialLayerListConfig, currentPath);
|
|
||||||
}
|
|
||||||
// TODO: Handle null when converting to TS
|
// TODO: Handle null when converting to TS
|
||||||
this._handleStoreChanges();
|
this._handleStoreChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this._globalSyncUnsubscribe) {
|
||||||
|
this._globalSyncUnsubscribe();
|
||||||
|
}
|
||||||
|
if (this._appSyncUnsubscribe) {
|
||||||
|
this._appSyncUnsubscribe();
|
||||||
|
}
|
||||||
|
if (this._globalSyncChangeMonitorSubscription) {
|
||||||
|
this._globalSyncChangeMonitorSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up app state filters
|
||||||
|
const { filterManager } = getData().query;
|
||||||
|
filterManager.filters.forEach((filter) => {
|
||||||
|
if (filter.$state.store === esFilters.FilterStateStore.APP_STATE) {
|
||||||
|
filterManager.removeFilter(filter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getCoreChrome().setBreadcrumbs([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasUnsavedChanges() {
|
||||||
|
return this.props.hasUnsavedChanges(this.props.savedMap, this.state.initialLayerListConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
_setBreadcrumbs = () => {
|
||||||
|
getCoreChrome().setBreadcrumbs([
|
||||||
|
{
|
||||||
|
text: i18n.translate('xpack.maps.mapController.mapsBreadcrumbLabel', {
|
||||||
|
defaultMessage: 'Maps',
|
||||||
|
}),
|
||||||
|
onClick: () => {
|
||||||
|
if (this._hasUnsavedChanges()) {
|
||||||
|
const navigateAway = window.confirm(unsavedChangesWarning);
|
||||||
|
if (navigateAway) {
|
||||||
|
goToSpecifiedPath('/');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
goToSpecifiedPath('/');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ text: this.props.savedMap.title },
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
_updateFromGlobalState = ({ changes, state: globalState }) => {
|
_updateFromGlobalState = ({ changes, state: globalState }) => {
|
||||||
if (!changes || !globalState) {
|
if (!changes || !globalState) {
|
||||||
return;
|
return;
|
||||||
|
@ -120,29 +163,6 @@ export class MapsAppView extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this._globalSyncUnsubscribe) {
|
|
||||||
this._globalSyncUnsubscribe();
|
|
||||||
}
|
|
||||||
if (this._appSyncUnsubscribe) {
|
|
||||||
this._appSyncUnsubscribe();
|
|
||||||
}
|
|
||||||
if (this._visibleSubscription) {
|
|
||||||
this._visibleSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this._globalSyncChangeMonitorSubscription) {
|
|
||||||
this._globalSyncChangeMonitorSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up app state filters
|
|
||||||
const { filterManager } = getData().query;
|
|
||||||
filterManager.filters.forEach((filter) => {
|
|
||||||
if (filter.$state.store === esFilters.FilterStateStore.APP_STATE) {
|
|
||||||
filterManager.removeFilter(filter);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_getInitialLayersFromUrlParam() {
|
_getInitialLayersFromUrlParam() {
|
||||||
const locationSplit = window.location.href.split('?');
|
const locationSplit = window.location.href.split('?');
|
||||||
if (locationSplit.length <= 1) {
|
if (locationSplit.length <= 1) {
|
||||||
|
@ -301,13 +321,9 @@ export class MapsAppView extends React.Component {
|
||||||
this._getInitialLayersFromUrlParam()
|
this._getInitialLayersFromUrlParam()
|
||||||
);
|
);
|
||||||
this.props.replaceLayerList(layerList);
|
this.props.replaceLayerList(layerList);
|
||||||
this.setState(
|
this.setState({
|
||||||
{
|
initialLayerListConfig: copyPersistentState(layerList),
|
||||||
initialLayerListConfig: copyPersistentState(layerList),
|
});
|
||||||
savedMap,
|
|
||||||
},
|
|
||||||
this._initBreadcrumbUpdater
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateFiltersAndDispatch = (filters) => {
|
_updateFiltersAndDispatch = (filters) => {
|
||||||
|
@ -407,18 +423,10 @@ export class MapsAppView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderTopNav() {
|
_renderTopNav() {
|
||||||
const {
|
const { query, time, savedQuery, indexPatterns } = this.state;
|
||||||
query,
|
const { savedMap, refreshConfig, isFullScreen } = this.props;
|
||||||
time,
|
|
||||||
savedQuery,
|
|
||||||
initialLayerListConfig,
|
|
||||||
isVisible,
|
|
||||||
indexPatterns,
|
|
||||||
currentPath,
|
|
||||||
} = this.state;
|
|
||||||
const { savedMap, refreshConfig } = this.props;
|
|
||||||
|
|
||||||
return isVisible ? (
|
return !isFullScreen ? (
|
||||||
<MapsTopNavMenu
|
<MapsTopNavMenu
|
||||||
savedMap={savedMap}
|
savedMap={savedMap}
|
||||||
query={query}
|
query={query}
|
||||||
|
@ -434,7 +442,6 @@ export class MapsAppView extends React.Component {
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
initialLayerListConfig={initialLayerListConfig}
|
|
||||||
indexPatterns={indexPatterns}
|
indexPatterns={indexPatterns}
|
||||||
updateFiltersAndDispatch={this._updateFiltersAndDispatch}
|
updateFiltersAndDispatch={this._updateFiltersAndDispatch}
|
||||||
onQuerySaved={(query) => {
|
onQuerySaved={(query) => {
|
||||||
|
@ -448,7 +455,7 @@ export class MapsAppView extends React.Component {
|
||||||
this._updateStateFromSavedQuery(query);
|
this._updateStateFromSavedQuery(query);
|
||||||
}}
|
}}
|
||||||
syncAppAndGlobalState={this._syncAppAndGlobalState}
|
syncAppAndGlobalState={this._syncAppAndGlobalState}
|
||||||
currentPath={currentPath}
|
setBreadcrumbs={this._setBreadcrumbs}
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -416,3 +416,23 @@ export const areLayersLoaded = createSelector(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export function hasUnsavedChanges(
|
||||||
|
state: MapStoreState,
|
||||||
|
savedMap: unknown,
|
||||||
|
initialLayerListConfig: LayerDescriptor[]
|
||||||
|
) {
|
||||||
|
const layerListConfigOnly = copyPersistentState(getLayerListRaw(state));
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
const savedLayerList = savedMap.getLayerList();
|
||||||
|
|
||||||
|
return !savedLayerList
|
||||||
|
? !_.isEqual(layerListConfigOnly, initialLayerListConfig)
|
||||||
|
: // savedMap stores layerList as a JSON string using JSON.stringify.
|
||||||
|
// JSON.stringify removes undefined properties from objects.
|
||||||
|
// savedMap.getLayerList converts the JSON string back into Javascript array of objects.
|
||||||
|
// Need to perform the same process for layerListConfigOnly to compare apples to apples
|
||||||
|
// and avoid undefined properties in layerListConfigOnly triggering unsaved changes.
|
||||||
|
!_.isEqual(JSON.parse(JSON.stringify(layerListConfigOnly)), savedLayerList);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue