Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sticky notes #7181

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
49599ff
Create StickyNotes DB entity
philemone Oct 28, 2024
84a7c16
Add PoC httpApiService for StickyNotes - add slick migration
philemone Nov 4, 2024
39a8317
Add put and delete endpoints, use value classes for ID in stickyNotes…
philemone Nov 6, 2024
290f2b3
Add StickyNote PoC to FE
philemone Nov 8, 2024
8ea61c2
Resize and update StickyNotes
philemone Nov 12, 2024
8467d28
Handle note removal
philemone Nov 13, 2024
777f6f2
Remove some unused fragments
philemone Nov 13, 2024
cb34708
Update openApi definition
philemone Nov 13, 2024
b7ceed3
Resize stickyNote without visual-lag
philemone Nov 14, 2024
bf039c6
Disable stickyNotes when scenario is not saved
philemone Nov 14, 2024
3cf57bc
Show/hide tools on graph actions
philemone Nov 14, 2024
6824e7f
Edit stickyNote on graph
philemone Nov 14, 2024
84094a5
Fix some suggestions made by rabbit
philemone Nov 15, 2024
61ddef0
Update openApi definitions
philemone Nov 15, 2024
96951b9
Add white characters to textarea in stickyNote markdown editor
philemone Nov 15, 2024
2452031
Allow focus to stay in markdown editor
philemone Nov 19, 2024
b2bc9f3
Allow switch viewer to editor witgh left mouse click
philemone Nov 19, 2024
e2ba757
Add stickyNotes length and count validation, add stickyNotes configur…
philemone Nov 19, 2024
7886c31
Remove node specific code from StickyNotePreview, update openApi defs
philemone Nov 20, 2024
3d6f85a
Add some fixes, improve types, add max width and height for stickyNote
philemone Nov 20, 2024
f5f0db7
Add STICKY_NOTE_CONSTRAINTS with config values
philemone Nov 20, 2024
d45c227
Reuse common code, restore default color, remove duplicated update me…
philemone Nov 20, 2024
9094b21
Restore CSS class, remove unused imports
philemone Nov 20, 2024
f5a31de
Remove stickyNotePanel, add stickyNote to creatos panel
philemone Nov 20, 2024
06fa563
Update cypress test
philemone Nov 21, 2024
fe751f1
Add Changelog and migrationGuide entries
philemone Nov 25, 2024
63c7064
Updated snapshots (#7275)
github-actions[bot] Dec 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions designer/client/cypress/e2e/creatorToolbar.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe("Creator toolbar", () => {
cy.contains(/^types$/i).click();
cy.contains(/^services$/i).click();
cy.contains(/^sinks$/i).click();
cy.contains(/^stickyNotes$/i).click();
cy.reload();
cy.get("@toolbar").matchImage();
cy.get("@toolbar").find("input").type("var");
Expand Down
11 changes: 11 additions & 0 deletions designer/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions designer/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"d3-transition": "3.0.1",
"d3-zoom": "3.0.0",
"dagre": "0.8.5",
"dompurify": "3.2.0",
"event-from": "1.0.0",
"file-saver": "2.0.5",
"flattenizer": "1.1.1",
Expand Down
2 changes: 2 additions & 0 deletions designer/client/src/actions/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type ActionTypes =
| "DELETE_NODES"
| "NODES_CONNECTED"
| "NODES_DISCONNECTED"
| "STICKY_NOTES_UPDATED"
| "STICKY_NOTE_DELETED"
| "VALIDATION_RESULT"
| "COPY_SELECTION"
| "CUT_SELECTION"
Expand Down
7 changes: 7 additions & 0 deletions designer/client/src/actions/nk/assignSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ export type FeaturesSettings = {
redirectAfterArchive: boolean;
usageStatisticsReports: UsageStatisticsReports;
surveySettings: SurveySettings;
stickyNotesSettings: StickyNotesSettings;
};

export type StickyNotesSettings = {
maxContentLength: number;
maxNotesCount: number;
enabled: boolean;
};

export type TestDataSettings = {
Expand Down
43 changes: 43 additions & 0 deletions designer/client/src/actions/nk/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { getProcessDefinitionData } from "../../reducers/selectors/settings";
import { ProcessDefinitionData, ScenarioGraph } from "../../types";
import { ThunkAction } from "../reduxTypes";
import HttpService from "./../../http/HttpService";
import { layoutChanged, Position } from "./ui/layout";
import { flushSync } from "react-dom";
import { Dimensions, StickyNote } from "../../common/StickyNote";

export type ScenarioActions =
| { type: "CORRECT_INVALID_SCENARIO"; processDefinitionData: ProcessDefinitionData }
Expand All @@ -17,6 +20,7 @@ export function fetchProcessToDisplay(processName: ProcessName, versionId?: Proc

return HttpService.fetchProcessDetails(processName, versionId).then((response) => {
dispatch(displayTestCapabilities(processName, response.data.scenarioGraph));
dispatch(fetchStickyNotesForScenario(processName, response.data.processVersionId));
philemone marked this conversation as resolved.
Show resolved Hide resolved
dispatch({
type: "DISPLAY_PROCESS",
scenario: response.data,
Expand Down Expand Up @@ -56,6 +60,45 @@ export function displayTestCapabilities(processName: ProcessName, scenarioGraph:
);
}

const refreshStickyNotes = (dispatch, scenarioName: string, scenarioVersionId: number) => {
return HttpService.getStickyNotes(scenarioName, scenarioVersionId).then((stickyNotes) => {
flushSync(() => {
dispatch({ type: "STICKY_NOTES_UPDATED", stickyNotes: stickyNotes.data });
dispatch(layoutChanged());
});
});
};
philemone marked this conversation as resolved.
Show resolved Hide resolved

export function fetchStickyNotesForScenario(scenarioName: string, scenarioVersionId: number): ThunkAction {
return (dispatch) => refreshStickyNotes(dispatch, scenarioName, scenarioVersionId);
}

export function stickyNoteUpdated(scenarioName: string, scenarioVersionId: number, stickyNote: StickyNote): ThunkAction {
return (dispatch) => {
HttpService.updateStickyNote(scenarioName, scenarioVersionId, stickyNote).then((_) => {
refreshStickyNotes(dispatch, scenarioName, scenarioVersionId);
});
};
}
philemone marked this conversation as resolved.
Show resolved Hide resolved

export function stickyNoteDeleted(scenarioName: string, stickyNoteId: number): ThunkAction {
return (dispatch) => {
HttpService.deleteStickyNote(scenarioName, stickyNoteId).then(() => {
flushSync(() => {
dispatch({ type: "STICKY_NOTE_DELETED", stickyNoteId });
});
});
};
}
philemone marked this conversation as resolved.
Show resolved Hide resolved

export function stickyNoteAdded(scenarioName: string, scenarioVersionId: number, position: Position, dimensions: Dimensions): ThunkAction {
return (dispatch) => {
HttpService.addStickyNote(scenarioName, scenarioVersionId, position, dimensions).then((_) => {
refreshStickyNotes(dispatch, scenarioName, scenarioVersionId);
});
};
}

export function displayCurrentProcessVersion(processName: ProcessName) {
return fetchProcessToDisplay(processName);
}
Expand Down
5 changes: 3 additions & 2 deletions designer/client/src/actions/notificationActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ export function success(message: string): Action {
});
}

export function error(message: string): Action {
//TODO please take a look at this method and my changes, am I wrong or was it incomplete (without `error` and `showErrorText`) and had incomplete logic
export function error(message: string, error?: string, showErrorText?: boolean): Action {
return Notifications.error({
autoDismiss: 10,
children: <Notification type={"error"} icon={<InfoOutlinedIcon />} message={message} />,
children: <Notification type={"error"} icon={<InfoOutlinedIcon />} message={showErrorText && error ? error : message} />,
});
}

Expand Down
3 changes: 3 additions & 0 deletions designer/client/src/assets/json/nodeAttributes.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
"Aggregate": {
"name": "Aggregate"
},
"StickyNote": {
"name": "StickyNote"
},
"CustomNode": {
"name": "CustomNode"
},
Expand Down
15 changes: 15 additions & 0 deletions designer/client/src/common/StickyNote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { LayoutData } from "../types";

export type Dimensions = { width: number; height: number };

export interface StickyNote {
id?: string;
noteId: number;
content: string;
layoutData: LayoutData;
dimensions: Dimensions;
color: string;
targetEdge?: string;
editedBy: string;
editedAt: string;
}
philemone marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 6 additions & 1 deletion designer/client/src/components/ComponentPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { ComponentIcon } from "./toolbars/creator/ComponentIcon";
import { alpha, styled, useTheme } from "@mui/material";
import { blend } from "@mui/system";
import { blendLighten, getBorderColor } from "../containers/theme/helpers";
import { StickyNotePreview } from "./StickyNotePreview";
import { StickyNoteType } from "../types/stickyNote";

export function ComponentPreview({ node, isActive, isOver }: { node: NodeType; isActive?: boolean; isOver?: boolean }): JSX.Element {
const theme = useTheme();
Expand Down Expand Up @@ -74,7 +76,10 @@ export function ComponentPreview({ node, isActive, isOver }: { node: NodeType; i
}));

const colors = isOver ? nodeColorsHover : nodeColors;
return (

return node?.type === StickyNoteType ? (
<StickyNotePreview isActive={isActive} isOver={isOver} />
) : (
<div className={cx(colors, nodeStyles)}>
<div className={cx(imageStyles, imageColors)}>
<ComponentIcon node={node} />
Expand Down
62 changes: 62 additions & 0 deletions designer/client/src/components/StickyNotePreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { css, cx } from "@emotion/css";
import React from "react";
import { BORDER_RADIUS, CONTENT_PADDING, iconBackgroundSize, iconSize } from "./graph/EspNode/esp";
import { PreloadedIcon, stickyNoteIconSrc } from "./toolbars/creator/ComponentIcon";
import { alpha, useTheme } from "@mui/material";
import { getBorderColor, getStickyNoteBackgroundColor } from "../containers/theme/helpers";
import { STICKY_NOTE_CONSTRAINTS, STICKY_NOTE_DEFAULT_COLOR } from "./graph/EspNode/stickyNote";

const PREVIEW_SCALE = 0.9;
const ACTIVE_ROTATION = 2;
const INACTIVE_SCALE = 1.5;

export function StickyNotePreview({ isActive, isOver }: { isActive?: boolean; isOver?: boolean }): JSX.Element {
const theme = useTheme();
const scale = isOver ? 1 : PREVIEW_SCALE;
const rotation = isActive ? (isOver ? -ACTIVE_ROTATION : ACTIVE_ROTATION) : 0;
const finalScale = isActive ? 1 : INACTIVE_SCALE;

const nodeStyles = css({
position: "relative",
width: STICKY_NOTE_CONSTRAINTS.DEFAULT_WIDTH,
height: STICKY_NOTE_CONSTRAINTS.DEFAULT_HEIGHT,
borderRadius: BORDER_RADIUS,
boxSizing: "content-box",
display: "inline-flex",
filter: `drop-shadow(0 4px 8px ${alpha(theme.palette.common.black, 0.5)})`,
borderWidth: 0.5,
borderStyle: "solid",
transformOrigin: "80% 50%",
transform: `translate(-80%, -50%) scale(${scale}) rotate(${rotation}deg) scale(${finalScale})`,
opacity: isActive ? undefined : 0,
transition: "all .5s, opacity .3s",
willChange: "transform, opacity, border-color, background-color",
});

const colors = css({
opacity: 0.5,
borderColor: getBorderColor(theme),
backgroundColor: getStickyNoteBackgroundColor(theme, STICKY_NOTE_DEFAULT_COLOR).main,
});

const imageStyles = css({
padding: iconSize / 2 - CONTENT_PADDING / 2,
margin: CONTENT_PADDING / 2,
borderRadius: BORDER_RADIUS,
width: iconBackgroundSize / 2,
height: iconBackgroundSize / 2,
color: theme.palette.common.black,
"> svg": {
height: iconSize,
width: iconSize,
},
});

return (
<div className={cx(colors, nodeStyles)}>
<div className={cx(imageStyles, colors)}>
<PreloadedIcon src={stickyNoteIconSrc} />
</div>
</div>
);
}
129 changes: 129 additions & 0 deletions designer/client/src/components/graph/EspNode/stickyNote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Theme } from "@mui/material";
import { dia, shapes, util, V } from "jointjs";
import { getBorderColor } from "../../../containers/theme/helpers";
import { StickyNote } from "../../../common/StickyNote";
import { marked } from "marked";
import { StickyNoteElement } from "../StickyNoteElement";
import MarkupNodeJSON = dia.MarkupNodeJSON;
import DOMPurify from "dompurify";

export const STICKY_NOTE_CONSTRAINTS = {
MIN_WIDTH: 100,
MAX_WIDTH: 3000,
DEFAULT_WIDTH: 300,
MIN_HEIGHT: 100,
MAX_HEIGHT: 3000,
DEFAULT_HEIGHT: 250,
} as const;

export const BORDER_RADIUS = 3;
export const CONTENT_PADDING = 5;
export const ICON_SIZE = 20;
export const STICKY_NOTE_DEFAULT_COLOR = "#eae672";
export const MARKDOWN_EDITOR_NAME = "markdown-editor";

const border: dia.MarkupNodeJSON = {
selector: "border",
tagName: "path",
className: "body",
attributes: {
width: STICKY_NOTE_CONSTRAINTS.DEFAULT_WIDTH,
height: STICKY_NOTE_CONSTRAINTS.DEFAULT_HEIGHT,
strokeWidth: 1,
fill: "none",
rx: BORDER_RADIUS,
},
};

const icon: dia.MarkupNodeJSON = {
selector: "icon",
tagName: "use",
attributes: {
opacity: 1,
width: ICON_SIZE,
height: ICON_SIZE,
x: ICON_SIZE / 2,
y: ICON_SIZE / 2,
},
};

const body: dia.MarkupNodeJSON = {
selector: "body",
tagName: "path",
};

const renderer = new marked.Renderer();
renderer.link = function (href, title, text) {
return `<a target="_blank" rel="noopener noreferrer" href="${href}">${text}</a>`;
};
renderer.image = function (href, title, text) {
// SVG don't support HTML img inside foreignObject
return `<a target="_blank" rel="noopener noreferrer" href="${href}">${text} (attached img)</a>`;
};
philemone marked this conversation as resolved.
Show resolved Hide resolved

const foreignObject = (stickyNote: StickyNote): MarkupNodeJSON => {
let parsed;
try {
parsed = DOMPurify.sanitize(marked.parse(stickyNote.content, { renderer }), { ADD_ATTR: ["target"] });
} catch (error) {
console.error("Failed to parse markdown:", error);
parsed = "Error: Could not parse content. See error logs in console";
}
const singleMarkupNode = util.svg/* xml */ `
<foreignObject @selector="foreignObject">
<div @selector="sticky-note-content" class="sticky-note-content">
<textarea @selector="${MARKDOWN_EDITOR_NAME}" class="sticky-note-markdown-editor" name="${MARKDOWN_EDITOR_NAME}" autocomplete="off" disabled="disabled"></textarea>
<div @selector="markdown" class="sticky-note-markdown">${parsed}</div>
</div>
</foreignObject>
`[0];
return singleMarkupNode as MarkupNodeJSON;
};
philemone marked this conversation as resolved.
Show resolved Hide resolved

export const stickyNotePath = "M 0 0 L 10 0 C 10 2.6667 10 5.3333 10 8 C 10 10 9 10 8 10 L 0 10 L 0 0";

const defaults = (theme: Theme) =>
util.defaultsDeep(
{
size: {
width: STICKY_NOTE_CONSTRAINTS.DEFAULT_WIDTH,
height: STICKY_NOTE_CONSTRAINTS.DEFAULT_HEIGHT,
},
attrs: {
body: {
refD: stickyNotePath,
strokeWidth: 2,
fill: "#eae672",
filter: {
name: "dropShadow",
args: {
dx: 1,
dy: 1,
blur: 5,
opacity: 0.4,
},
},
},
foreignObject: {
width: STICKY_NOTE_CONSTRAINTS.DEFAULT_WIDTH,
height: STICKY_NOTE_CONSTRAINTS.DEFAULT_HEIGHT - ICON_SIZE - CONTENT_PADDING * 4,
y: CONTENT_PADDING * 4 + ICON_SIZE,
fill: getBorderColor(theme),
},
border: {
refD: stickyNotePath,
stroke: getBorderColor(theme),
},
},
},
shapes.devs.Model.prototype.defaults,
);

const protoProps = (theme: Theme, stickyNote: StickyNote) => {
return {
markup: [body, border, foreignObject(stickyNote), icon],
};
};

export const StickyNoteShape = (theme: Theme, stickyNote: StickyNote) =>
StickyNoteElement(defaults(theme), protoProps(theme, stickyNote)) as typeof shapes.devs.Model;
Loading
Loading