mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard: Fixes save modal rendered ontop of save drawer (#46916)
* Dashboard: Fixes save modal rendered ontop of save drawer * removed commented line * Simplified dismiss -> hideModal mapping * Fixed issue with new dashboard * Fixing issues
This commit is contained in:
parent
c00f488f89
commit
3516821012
@ -7,7 +7,7 @@ interface ModalsContextState {
|
|||||||
hideModal: () => void;
|
hideModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalsContext = React.createContext<ModalsContextState>({
|
export const ModalsContext = React.createContext<ModalsContextState>({
|
||||||
component: null,
|
component: null,
|
||||||
props: {},
|
props: {},
|
||||||
showModal: () => {},
|
showModal: () => {},
|
||||||
|
@ -62,7 +62,7 @@ export { Modal } from './Modal/Modal';
|
|||||||
export { ModalHeader } from './Modal/ModalHeader';
|
export { ModalHeader } from './Modal/ModalHeader';
|
||||||
export { ModalTabsHeader } from './Modal/ModalTabsHeader';
|
export { ModalTabsHeader } from './Modal/ModalTabsHeader';
|
||||||
export { ModalTabContent } from './Modal/ModalTabContent';
|
export { ModalTabContent } from './Modal/ModalTabContent';
|
||||||
export { ModalsProvider, ModalRoot, ModalsController } from './Modal/ModalsContext';
|
export { ModalsProvider, ModalRoot, ModalsController, ModalsContext } from './Modal/ModalsContext';
|
||||||
export { PageToolbar } from './PageLayout/PageToolbar';
|
export { PageToolbar } from './PageLayout/PageToolbar';
|
||||||
|
|
||||||
// Renderless
|
// Renderless
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
import { appEvents } from 'app/core/core';
|
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { Prompt } from 'react-router-dom';
|
import { Prompt } from 'react-router-dom';
|
||||||
import { DashboardModel } from '../../state/DashboardModel';
|
import { DashboardModel } from '../../state/DashboardModel';
|
||||||
import { each, filter, find } from 'lodash';
|
import { each, filter, find } from 'lodash';
|
||||||
@ -11,6 +10,8 @@ import { SaveLibraryPanelModal } from 'app/features/library-panels/components/Sa
|
|||||||
import { PanelModelWithLibraryPanel } from 'app/features/library-panels/types';
|
import { PanelModelWithLibraryPanel } from 'app/features/library-panels/types';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { discardPanelChanges, exitPanelEditor } from '../PanelEditor/state/actions';
|
import { discardPanelChanges, exitPanelEditor } from '../PanelEditor/state/actions';
|
||||||
|
import { ModalsContext } from '@grafana/ui';
|
||||||
|
import { appEvents } from 'app/core/app_events';
|
||||||
import { DashboardSavedEvent } from 'app/types/events';
|
import { DashboardSavedEvent } from 'app/types/events';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -20,19 +21,13 @@ export interface Props {
|
|||||||
interface State {
|
interface State {
|
||||||
original: object | null;
|
original: object | null;
|
||||||
originalPath?: string;
|
originalPath?: string;
|
||||||
modal: PromptModal | null;
|
|
||||||
blockedLocation?: H.Location | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PromptModal {
|
|
||||||
UnsavedChangesModal,
|
|
||||||
SaveLibraryPanelModal,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DashboardPrompt = React.memo(({ dashboard }: Props) => {
|
export const DashboardPrompt = React.memo(({ dashboard }: Props) => {
|
||||||
const [state, setState] = useState<State>({ original: null, modal: null });
|
const [state, setState] = useState<State>({ original: null });
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { original, originalPath, blockedLocation, modal } = state;
|
const { original, originalPath } = state;
|
||||||
|
const { showModal, hideModal } = useContext(ModalsContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This timeout delay is to wait for panels to load and migrate scheme before capturing the original state
|
// This timeout delay is to wait for panels to load and migrate scheme before capturing the original state
|
||||||
@ -40,14 +35,19 @@ export const DashboardPrompt = React.memo(({ dashboard }: Props) => {
|
|||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
const originalPath = locationService.getLocation().pathname;
|
const originalPath = locationService.getLocation().pathname;
|
||||||
const original = dashboard.getSaveModelClone();
|
const original = dashboard.getSaveModelClone();
|
||||||
|
setState({ originalPath, original });
|
||||||
setState({ originalPath, original, modal: null });
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
|
const savedEventUnsub = appEvents.subscribe(DashboardSavedEvent, () => {
|
||||||
|
const original = dashboard.getSaveModelClone();
|
||||||
|
setState({ originalPath, original });
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
savedEventUnsub.unsubscribe();
|
||||||
};
|
};
|
||||||
}, [dashboard]);
|
}, [dashboard, originalPath]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleUnload = (event: BeforeUnloadEvent) => {
|
const handleUnload = (event: BeforeUnloadEvent) => {
|
||||||
@ -65,28 +65,27 @@ export const DashboardPrompt = React.memo(({ dashboard }: Props) => {
|
|||||||
return () => window.removeEventListener('beforeunload', handleUnload);
|
return () => window.removeEventListener('beforeunload', handleUnload);
|
||||||
}, [dashboard, original]);
|
}, [dashboard, original]);
|
||||||
|
|
||||||
// Handle saved events
|
|
||||||
useEffect(() => {
|
|
||||||
const savedEventUnsub = appEvents.subscribe(DashboardSavedEvent, () => {
|
|
||||||
const original = dashboard.getSaveModelClone();
|
|
||||||
const originalPath = locationService.getLocation().pathname;
|
|
||||||
setState({ originalPath, original, modal: null });
|
|
||||||
|
|
||||||
if (blockedLocation) {
|
|
||||||
moveToBlockedLocationAfterReactStateUpdate(blockedLocation);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => savedEventUnsub.unsubscribe();
|
|
||||||
}, [dashboard, blockedLocation]);
|
|
||||||
|
|
||||||
const onHistoryBlock = (location: H.Location) => {
|
const onHistoryBlock = (location: H.Location) => {
|
||||||
const panelInEdit = dashboard.panelInEdit;
|
const panelInEdit = dashboard.panelInEdit;
|
||||||
const search = new URLSearchParams(location.search);
|
const search = new URLSearchParams(location.search);
|
||||||
|
|
||||||
// Are we leaving panel edit & library panel?
|
// Are we leaving panel edit & library panel?
|
||||||
if (panelInEdit && panelInEdit.libraryPanel && panelInEdit.hasChanged && !search.has('editPanel')) {
|
if (panelInEdit && panelInEdit.libraryPanel && panelInEdit.hasChanged && !search.has('editPanel')) {
|
||||||
setState({ ...state, modal: PromptModal.SaveLibraryPanelModal, blockedLocation: location });
|
showModal(SaveLibraryPanelModal, {
|
||||||
|
isUnsavedPrompt: true,
|
||||||
|
panel: dashboard.panelInEdit as PanelModelWithLibraryPanel,
|
||||||
|
folderId: dashboard.meta.folderId as number,
|
||||||
|
onConfirm: () => {
|
||||||
|
hideModal();
|
||||||
|
moveToBlockedLocationAfterReactStateUpdate(location);
|
||||||
|
},
|
||||||
|
onDiscard: () => {
|
||||||
|
dispatch(discardPanelChanges());
|
||||||
|
moveToBlockedLocationAfterReactStateUpdate(location);
|
||||||
|
hideModal();
|
||||||
|
},
|
||||||
|
onDismiss: hideModal,
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,57 +107,31 @@ export const DashboardPrompt = React.memo(({ dashboard }: Props) => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState({ ...state, modal: PromptModal.UnsavedChangesModal, blockedLocation: location });
|
showModal(UnsavedChangesModal, {
|
||||||
|
dashboard: dashboard,
|
||||||
|
onSaveSuccess: () => {
|
||||||
|
hideModal();
|
||||||
|
moveToBlockedLocationAfterReactStateUpdate(location);
|
||||||
|
},
|
||||||
|
onDiscard: () => {
|
||||||
|
setState({ ...state, original: null });
|
||||||
|
hideModal();
|
||||||
|
moveToBlockedLocationAfterReactStateUpdate(location);
|
||||||
|
},
|
||||||
|
onDismiss: hideModal,
|
||||||
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onHideModalAndMoveToBlockedLocation = () => {
|
return <Prompt when={true} message={onHistoryBlock} />;
|
||||||
setState({ ...state, modal: null });
|
|
||||||
moveToBlockedLocationAfterReactStateUpdate(blockedLocation);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Prompt when={true} message={onHistoryBlock} />
|
|
||||||
{modal === PromptModal.UnsavedChangesModal && (
|
|
||||||
<UnsavedChangesModal
|
|
||||||
dashboard={dashboard}
|
|
||||||
onSaveSuccess={() => {}} // Handled by DashboardSavedEvent above
|
|
||||||
onDiscard={() => {
|
|
||||||
// Clear original will allow us to leave without unsaved changes prompt
|
|
||||||
setState({ ...state, original: null, modal: null });
|
|
||||||
moveToBlockedLocationAfterReactStateUpdate(blockedLocation);
|
|
||||||
}}
|
|
||||||
onDismiss={() => {
|
|
||||||
setState({ ...state, modal: null, blockedLocation: null });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{modal === PromptModal.SaveLibraryPanelModal && (
|
|
||||||
<SaveLibraryPanelModal
|
|
||||||
isUnsavedPrompt
|
|
||||||
panel={dashboard.panelInEdit as PanelModelWithLibraryPanel}
|
|
||||||
folderId={dashboard.meta.folderId as number}
|
|
||||||
onConfirm={onHideModalAndMoveToBlockedLocation}
|
|
||||||
onDiscard={() => {
|
|
||||||
dispatch(discardPanelChanges());
|
|
||||||
setState({ ...state, modal: null });
|
|
||||||
moveToBlockedLocationAfterReactStateUpdate(blockedLocation);
|
|
||||||
}}
|
|
||||||
onDismiss={() => {
|
|
||||||
setState({ ...state, modal: null, blockedLocation: null });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
DashboardPrompt.displayName = 'DashboardPrompt';
|
DashboardPrompt.displayName = 'DashboardPrompt';
|
||||||
|
|
||||||
function moveToBlockedLocationAfterReactStateUpdate(location?: H.Location | null) {
|
function moveToBlockedLocationAfterReactStateUpdate(location?: H.Location | null) {
|
||||||
if (location) {
|
if (location) {
|
||||||
setTimeout(() => locationService.push(location!), 10);
|
setTimeout(() => locationService.push(location), 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import { css, cx } from '@emotion/css';
|
|||||||
import { Button, CustomScrollbar, Icon, IconName, PageToolbar, stylesFactory, useForceUpdate } from '@grafana/ui';
|
import { Button, CustomScrollbar, Icon, IconName, PageToolbar, stylesFactory, useForceUpdate } from '@grafana/ui';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
|
||||||
import { DashboardModel } from '../../state/DashboardModel';
|
import { DashboardModel } from '../../state/DashboardModel';
|
||||||
import { SaveDashboardAsButton, SaveDashboardButton } from '../SaveDashboard/SaveDashboardButton';
|
import { SaveDashboardAsButton, SaveDashboardButton } from '../SaveDashboard/SaveDashboardButton';
|
||||||
import { VariableEditorContainer } from '../../../variables/editor/VariableEditorContainer';
|
import { VariableEditorContainer } from '../../../variables/editor/VariableEditorContainer';
|
||||||
@ -131,7 +130,6 @@ export function DashboardSettings({ dashboard, editview }: Props) {
|
|||||||
|
|
||||||
const onPostSave = () => {
|
const onPostSave = () => {
|
||||||
dashboard.meta.hasUnsavedFolderChange = false;
|
dashboard.meta.hasUnsavedFolderChange = false;
|
||||||
dashboardWatcher.reloadPage();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const folderTitle = dashboard.meta.folderTitle;
|
const folderTitle = dashboard.meta.folderTitle;
|
||||||
|
@ -16,6 +16,7 @@ export const JsonEditorSettings: React.FC<Props> = ({ dashboard }) => {
|
|||||||
const onBlur = (value: string) => {
|
const onBlur = (value: string) => {
|
||||||
setDashboardJson(value);
|
setDashboardJson(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
getDashboardSrv()
|
getDashboardSrv()
|
||||||
.saveJSONDashboard(dashboardJson)
|
.saveJSONDashboard(dashboardJson)
|
||||||
@ -23,6 +24,7 @@ export const JsonEditorSettings: React.FC<Props> = ({ dashboard }) => {
|
|||||||
dashboardWatcher.reloadPage();
|
dashboardWatcher.reloadPage();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -51,8 +51,9 @@ export const SaveDashboardForm = ({
|
|||||||
dashboard.resetOriginalTime();
|
dashboard.resetOriginalTime();
|
||||||
}
|
}
|
||||||
onSuccess();
|
onSuccess();
|
||||||
|
} else {
|
||||||
|
setSaving(false);
|
||||||
}
|
}
|
||||||
setSaving(false);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ register, errors }) => (
|
{({ register, errors }) => (
|
||||||
@ -96,7 +97,7 @@ export const SaveDashboardForm = ({
|
|||||||
icon={saving ? 'fa fa-spinner' : undefined}
|
icon={saving ? 'fa fa-spinner' : undefined}
|
||||||
aria-label={selectors.pages.SaveDashboardModal.save}
|
aria-label={selectors.pages.SaveDashboardModal.save}
|
||||||
>
|
>
|
||||||
{saving ? '' : 'Save'}
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
{!saveModel.hasChanges && <div>No changes to save</div>}
|
{!saveModel.hasChanges && <div>No changes to save</div>}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
Loading…
Reference in New Issue
Block a user