diff --git a/designer/client/src/actions/notificationActions.tsx b/designer/client/src/actions/notificationActions.tsx index bfb48dc396a..c11b61d8c37 100644 --- a/designer/client/src/actions/notificationActions.tsx +++ b/designer/client/src/actions/notificationActions.tsx @@ -2,6 +2,7 @@ import React from "react"; import Notifications from "react-notification-system-redux"; import CheckCircleOutlinedIcon from "@mui/icons-material/CheckCircleOutlined"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; +import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined"; import Notification from "../components/notifications/Notification"; import { Action } from "./reduxTypes"; @@ -26,3 +27,10 @@ export function info(message: string): Action { children: } message={message} />, }); } + +export function warn(message: string): Action { + return Notifications.warning({ + autoDismiss: 10, + children: } message={message} />, + }); +} diff --git a/designer/client/src/components/graph/Graph.tsx b/designer/client/src/components/graph/Graph.tsx index 124a608f173..427958552f2 100644 --- a/designer/client/src/components/graph/Graph.tsx +++ b/designer/client/src/components/graph/Graph.tsx @@ -49,6 +49,8 @@ import { handleGraphEvent } from "./utils/graphUtils"; import { StickyNote } from "../../common/StickyNote"; import { StickyNoteElement, StickyNoteElementView } from "./StickyNoteElement"; import { STICKY_NOTE_CONSTRAINTS } from "./EspNode/stickyNote"; +import { NotificationActions } from "../../http/HttpService"; +import i18next from "i18next"; function clamp(number: number, max: number) { return Math.round(Math.min(max, Math.max(-max, number))); @@ -65,6 +67,7 @@ type Props = GraphProps & { theme: Theme; translation: UseTranslationResponse; handleStatisticsEvent: (event: TrackEventParams) => void; + notifications: NotificationActions; }; export const nuGraphNamespace = { @@ -233,6 +236,15 @@ export class Graph extends React.Component { } if (isStickyNoteElement(cell.model)) { this.processGraphPaper.hideTools(); + if (!this.props.isPristine) { + this.props.notifications.warn( + i18next.t( + "notification.warn.cannotDeleteOnUnsavedVersion", + "Save scenario before making any changes to sticky notes", + ), + ); + return; + } cell.showTools(); const updatedStickyNote = getStickyNoteCopyFromCell(this.props.stickyNotes, cell.model); if (!updatedStickyNote) return; @@ -382,6 +394,7 @@ export class Graph extends React.Component { if (this.props.isFragment === true) return; this.processGraphPaper.hideTools(); if (isStickyNoteElement(cellView.model)) { + if (!this.props.isPristine) return; showStickyNoteTools(cellView); } if (this.props.nodeSelectionEnabled) { @@ -469,6 +482,12 @@ export class Graph extends React.Component { this.graph.on(Events.CELL_RESIZED, (cell: dia.Element) => { if (isStickyNoteElement(cell)) { + if (!this.props.isPristine) { + this.props.notifications.warn( + i18next.t("notification.warn.cannotDeleteOnUnsavedVersion", "Save scenario before resizing sticky note"), + ); + return; + } const updatedStickyNote = getStickyNoteCopyFromCell(this.props.stickyNotes, cell); if (!updatedStickyNote) return; const position = cell.get("position"); @@ -490,6 +509,12 @@ export class Graph extends React.Component { this.graph.on(Events.CELL_CONTENT_UPDATED, (cell: dia.Element, content: string) => { if (isStickyNoteElement(cell)) { + if (!this.props.isPristine) { + this.props.notifications.warn( + i18next.t("notification.warn.cannotDeleteOnUnsavedVersion", "Save scenario before updating sticky note"), + ); + return; + } const updatedStickyNote = getStickyNoteCopyFromCell(this.props.stickyNotes, cell); if (!updatedStickyNote) return; if (updatedStickyNote.content == content) return; @@ -500,6 +525,12 @@ export class Graph extends React.Component { this.graph.on(Events.CELL_DELETED, (cell: dia.Element) => { if (isStickyNoteElement(cell)) { + if (!this.props.isPristine) { + this.props.notifications.warn( + i18next.t("notification.warn.cannotDeleteOnUnsavedVersion", "Save scenario before deleting sticky note"), + ); + return; + } const noteId = Number(cell.get("noteId")); this.deleteStickyNote(this.props.scenario.name, noteId); } diff --git a/designer/client/src/components/graph/GraphWrapped.tsx b/designer/client/src/components/graph/GraphWrapped.tsx index d9b5351e844..4847d6123d7 100644 --- a/designer/client/src/components/graph/GraphWrapped.tsx +++ b/designer/client/src/components/graph/GraphWrapped.tsx @@ -1,7 +1,7 @@ import { useTheme } from "@mui/material"; import React, { forwardRef, useRef } from "react"; import { useTranslation } from "react-i18next"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { useForkRef } from "rooks"; import { useEventTracking } from "../../containers/event-tracking"; import { getProcessCategory, getSelectionState, isPristine } from "../../reducers/selectors/graph"; @@ -12,10 +12,13 @@ import { Graph } from "./Graph"; import { GraphStyledWrapper } from "./graphStyledWrapper"; import { NodeDescriptionPopover } from "./NodeDescriptionPopover"; import { GraphProps } from "./types"; +import { bindActionCreators } from "redux"; +import * as NotificationActions from "../../actions/notificationActions"; // Graph wrapped to make partial (for now) refactor to TS and hooks export default forwardRef(function GraphWrapped(props, forwardedRef): JSX.Element { const { openNodeWindow } = useWindows(); + const dispatch = useDispatch(); const userSettings = useSelector(getUserSettings); const pristine = useSelector(isPristine); const processCategory = useSelector(getProcessCategory); @@ -25,7 +28,7 @@ export default forwardRef(function GraphWrapped(props, forwar const theme = useTheme(); const translation = useTranslation(); const { trackEvent } = useEventTracking(); - + const notifications = bindActionCreators(NotificationActions, dispatch); const graphRef = useRef(); const ref = useForkRef(graphRef, forwardedRef); @@ -45,6 +48,7 @@ export default forwardRef(function GraphWrapped(props, forwar theme={theme} translation={translation} handleStatisticsEvent={trackEvent} + notifications={notifications} /> diff --git a/designer/client/src/components/graph/StickyNoteElement.ts b/designer/client/src/components/graph/StickyNoteElement.ts index 9dcdcbad2dd..a9a1d7d5ef3 100644 --- a/designer/client/src/components/graph/StickyNoteElement.ts +++ b/designer/client/src/components/graph/StickyNoteElement.ts @@ -21,6 +21,7 @@ export const StickyNoteElementView = dia.ElementView.extend({ events: { "click textarea": "stopPropagation", "keydown textarea": "selectAll", + "blur textarea": "onChange", "focusout textarea": "onChange", "dblclick .sticky-note-content": "showEditor", }, @@ -46,6 +47,7 @@ export const StickyNoteElementView = dia.ElementView.extend({ onChange: function (evt) { this.model.trigger(Events.CELL_CONTENT_UPDATED, this.model, evt.target.value); + console.log(evt); this.model.attr(`${MARKDOWN_EDITOR_NAME}/props/value`, evt.target.value); this.model.attr(`${MARKDOWN_EDITOR_NAME}/props/disabled`, true); }, diff --git a/designer/client/src/http/HttpService.ts b/designer/client/src/http/HttpService.ts index bf5caa14a17..66a7b1d6fd2 100644 --- a/designer/client/src/http/HttpService.ts +++ b/designer/client/src/http/HttpService.ts @@ -125,9 +125,10 @@ export type ComponentUsageType = { lastAction: ProcessActionType; }; -type NotificationActions = { +export type NotificationActions = { success(message: string): void; error(message: string, error: string, showErrorText: boolean): void; + warn(message: string): void; }; export interface TestProcessResponse {