mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
177 lines
4.8 KiB
TypeScript
177 lines
4.8 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import * as H from 'history';
|
|
import React, { useContext, useEffect, useMemo } from 'react';
|
|
import { Prompt } from 'react-router';
|
|
|
|
import { locationService } from '@grafana/runtime';
|
|
import { Dashboard } from '@grafana/schema/dist/esm/index.gen';
|
|
import { ModalsContext, Modal, Button, useStyles2 } from '@grafana/ui';
|
|
import { contextSrv } from 'app/core/services/context_srv';
|
|
|
|
import { DashboardScene } from '../scene/DashboardScene';
|
|
|
|
interface DashboardPromptProps {
|
|
dashboard: DashboardScene;
|
|
}
|
|
|
|
export const DashboardPrompt = React.memo(({ dashboard }: DashboardPromptProps) => {
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
const originalPath = useMemo(() => locationService.getLocation().pathname, [dashboard]);
|
|
const { showModal, hideModal } = useContext(ModalsContext);
|
|
|
|
useEffect(() => {
|
|
const handleUnload = (event: BeforeUnloadEvent) => {
|
|
if (ignoreChanges(dashboard, dashboard.getInitialSaveModel())) {
|
|
return;
|
|
}
|
|
|
|
if (dashboard.state.isDirty) {
|
|
event.preventDefault();
|
|
// No browser actually displays this message anymore.
|
|
// But Chrome requires it to be defined else the popup won't show.
|
|
event.returnValue = '';
|
|
}
|
|
};
|
|
|
|
window.addEventListener('beforeunload', handleUnload);
|
|
return () => window.removeEventListener('beforeunload', handleUnload);
|
|
}, [dashboard]);
|
|
|
|
const onHistoryBlock = (location: H.Location) => {
|
|
// const panelInEdit = dashboard.state.editPanel;
|
|
// const search = new URLSearchParams(location.search);
|
|
|
|
// TODO: Are we leaving panel edit & library panel?
|
|
|
|
// if (panelInEdit && panelInEdit.libraryPanel && panelInEdit.hasChanged && !search.has('editPanel')) {
|
|
// showModal(SaveLibraryPanelModal, {
|
|
// isUnsavedPrompt: true,
|
|
// panel: dashboard.panelInEdit as PanelModelWithLibraryPanel,
|
|
// folderUid: dashboard.meta.folderUid ?? '',
|
|
// onConfirm: () => {
|
|
// hideModal();
|
|
// moveToBlockedLocationAfterReactStateUpdate(location);
|
|
// },
|
|
// onDiscard: () => {
|
|
// dispatch(discardPanelChanges());
|
|
// moveToBlockedLocationAfterReactStateUpdate(location);
|
|
// hideModal();
|
|
// },
|
|
// onDismiss: hideModal,
|
|
// });
|
|
// return false;
|
|
// }
|
|
|
|
// Are we still on the same dashboard?
|
|
if (originalPath === location.pathname) {
|
|
return true;
|
|
}
|
|
|
|
if (ignoreChanges(dashboard, dashboard.getInitialSaveModel())) {
|
|
return true;
|
|
}
|
|
|
|
if (!dashboard.state.isDirty) {
|
|
return true;
|
|
}
|
|
|
|
showModal(UnsavedChangesModal, {
|
|
dashboard,
|
|
onSaveDashboardClick: () => {
|
|
hideModal();
|
|
dashboard.openSaveDrawer({
|
|
onSaveSuccess: () => {
|
|
moveToBlockedLocationAfterReactStateUpdate(location);
|
|
},
|
|
});
|
|
},
|
|
|
|
onDiscard: () => {
|
|
dashboard.exitEditMode({ skipConfirm: true });
|
|
hideModal();
|
|
moveToBlockedLocationAfterReactStateUpdate(location);
|
|
},
|
|
onDismiss: hideModal,
|
|
});
|
|
|
|
return false;
|
|
};
|
|
|
|
return <Prompt when={true} message={onHistoryBlock} />;
|
|
});
|
|
|
|
DashboardPrompt.displayName = 'DashboardPrompt';
|
|
|
|
function moveToBlockedLocationAfterReactStateUpdate(location?: H.Location | null) {
|
|
if (location) {
|
|
setTimeout(() => locationService.push(location), 10);
|
|
}
|
|
}
|
|
|
|
interface UnsavedChangesModalProps {
|
|
onDiscard: () => void;
|
|
onDismiss: () => void;
|
|
onSaveDashboardClick?: () => void;
|
|
}
|
|
|
|
export const UnsavedChangesModal = ({ onDiscard, onDismiss, onSaveDashboardClick }: UnsavedChangesModalProps) => {
|
|
const styles = useStyles2(getStyles);
|
|
|
|
return (
|
|
<Modal
|
|
isOpen={true}
|
|
title="Unsaved changes"
|
|
onDismiss={onDismiss}
|
|
icon="exclamation-triangle"
|
|
className={styles.modal}
|
|
>
|
|
<h5>Do you want to save your changes?</h5>
|
|
<Modal.ButtonRow>
|
|
<Button variant="secondary" onClick={onDismiss} fill="outline">
|
|
Cancel
|
|
</Button>
|
|
<Button variant="destructive" onClick={onDiscard}>
|
|
Discard
|
|
</Button>
|
|
<Button onClick={onSaveDashboardClick}>Save dashboard</Button>
|
|
</Modal.ButtonRow>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
const getStyles = () => ({
|
|
modal: css({
|
|
width: '500px',
|
|
}),
|
|
});
|
|
|
|
/**
|
|
* For some dashboards and users changes should be ignored *
|
|
*/
|
|
export function ignoreChanges(current: DashboardScene | null, original?: Dashboard) {
|
|
if (!original) {
|
|
return true;
|
|
}
|
|
|
|
// Ignore changes if original is unsaved
|
|
if (original.version === 0) {
|
|
return true;
|
|
}
|
|
|
|
// Ignore changes if the user has been signed out
|
|
if (!contextSrv.isSignedIn) {
|
|
return true;
|
|
}
|
|
|
|
if (!current) {
|
|
return true;
|
|
}
|
|
|
|
const { canSave, fromScript, fromFile } = current.state.meta;
|
|
if (!contextSrv.isEditor && !canSave) {
|
|
return true;
|
|
}
|
|
|
|
return !canSave || fromScript || fromFile;
|
|
}
|