From 71c3a0a9f9c957c9147d10a29fec0d2cc5e139e4 Mon Sep 17 00:00:00 2001 From: Julian Wielga Date: Tue, 13 Aug 2024 12:37:04 +0200 Subject: [PATCH] description in window --- designer/client/package-lock.json | 14 +- designer/client/package.json | 2 +- .../node-modal/DescriptionOnlyContent.tsx | 48 +++++++ .../node-modal/NodeTypeDetailsContent.tsx | 86 +++++++---- .../node-modal/edge/EdgeDetailsContent.tsx | 22 +-- .../editors/expression/AceWithSettings.tsx | 36 ++++- .../graph/node-modal/node/NodeDetails.tsx | 134 ++++++++++++------ .../graph/node-modal/node/StyledHeader.tsx | 11 +- .../buttons/PropertiesButton.tsx | 24 ++-- .../client/src/containers/DescriptionView.tsx | 6 + .../client/src/containers/NussknackerApp.tsx | 11 +- .../src/containers/ScenarioDescription.tsx | 97 +++++-------- .../src/windowManager/ContentGetter.tsx | 12 +- .../src/windowManager/WindowContent.tsx | 3 +- .../client/src/windowManager/WindowKind.tsx | 1 + .../client/src/windowManager/useWindows.ts | 31 +++- designer/client/webpack.config.ts | 24 ++-- 17 files changed, 372 insertions(+), 190 deletions(-) create mode 100644 designer/client/src/components/graph/node-modal/DescriptionOnlyContent.tsx create mode 100644 designer/client/src/containers/DescriptionView.tsx diff --git a/designer/client/package-lock.json b/designer/client/package-lock.json index 7ca3ba13f2a..140221696e4 100644 --- a/designer/client/package-lock.json +++ b/designer/client/package-lock.json @@ -22,7 +22,7 @@ "@mui/lab": "5.0.0-alpha.165", "@mui/material": "5.15.7", "@touk/federated-component": "1.0.0", - "@touk/window-manager": "1.9.0-beta.4", + "@touk/window-manager": "1.9.0-beta.8", "ace-builds": "1.34.2", "axios": "1.6.7", "d3-transition": "3.0.1", @@ -6087,9 +6087,9 @@ } }, "node_modules/@touk/window-manager": { - "version": "1.9.0-beta.4", - "resolved": "https://registry.npmjs.org/@touk/window-manager/-/window-manager-1.9.0-beta.4.tgz", - "integrity": "sha512-hUIkUPE9dMk125g/lPw+2wUu3F5VnhvbPR/pSi38m8r9VjzOnNeDbHafAHqJZI0QCq2skcMQEMw9/RbzvguFqA==", + "version": "1.9.0-beta.8", + "resolved": "https://registry.npmjs.org/@touk/window-manager/-/window-manager-1.9.0-beta.8.tgz", + "integrity": "sha512-ztpDp34ZLYiXRjab3bMZw+Wn3f7T6Sxq5yUC+EsnJoxwFnPUpFlnJpPFu+cky9M6N9gX0RIcX5ETLKCIR7V+RA==", "hasInstallScript": true, "dependencies": { "@emotion/css": "11.11.2", @@ -32085,9 +32085,9 @@ } }, "@touk/window-manager": { - "version": "1.9.0-beta.4", - "resolved": "https://registry.npmjs.org/@touk/window-manager/-/window-manager-1.9.0-beta.4.tgz", - "integrity": "sha512-hUIkUPE9dMk125g/lPw+2wUu3F5VnhvbPR/pSi38m8r9VjzOnNeDbHafAHqJZI0QCq2skcMQEMw9/RbzvguFqA==", + "version": "1.9.0-beta.8", + "resolved": "https://registry.npmjs.org/@touk/window-manager/-/window-manager-1.9.0-beta.8.tgz", + "integrity": "sha512-ztpDp34ZLYiXRjab3bMZw+Wn3f7T6Sxq5yUC+EsnJoxwFnPUpFlnJpPFu+cky9M6N9gX0RIcX5ETLKCIR7V+RA==", "requires": { "@emotion/css": "11.11.2", "@emotion/react": "11.11.4", diff --git a/designer/client/package.json b/designer/client/package.json index 21062b24c0f..03f955ab6cc 100644 --- a/designer/client/package.json +++ b/designer/client/package.json @@ -15,7 +15,7 @@ "@mui/lab": "5.0.0-alpha.165", "@mui/material": "5.15.7", "@touk/federated-component": "1.0.0", - "@touk/window-manager": "1.9.0-beta.4", + "@touk/window-manager": "1.9.0-beta.8", "ace-builds": "1.34.2", "axios": "1.6.7", "d3-transition": "3.0.1", diff --git a/designer/client/src/components/graph/node-modal/DescriptionOnlyContent.tsx b/designer/client/src/components/graph/node-modal/DescriptionOnlyContent.tsx new file mode 100644 index 00000000000..068fa4d0fcd --- /dev/null +++ b/designer/client/src/components/graph/node-modal/DescriptionOnlyContent.tsx @@ -0,0 +1,48 @@ +import { Box } from "@mui/material"; +import { get } from "lodash"; +import React from "react"; +import { DescriptionView } from "../../../containers/DescriptionView"; +import { FieldType } from "./editors/field/Field"; +import { rowAceEditor } from "./NodeDetailsContent/NodeTableStyled"; +import { NodeField } from "./NodeField"; +import { NodeTypeDetailsContentProps, useNodeTypeDetailsContentLogic } from "./NodeTypeDetailsContent"; + +export function DescriptionOnlyContent({ + preview, + ...props +}: Pick & { + preview?: boolean; +}): JSX.Element { + const { setProperty, node } = useNodeTypeDetailsContentLogic({ ...props, errors: [] }); + const fieldName = "additionalFields.description"; + + return ( + <> + {!preview ? ( + + null} + setProperty={setProperty} + node={node} + isEditMode={true} + showValidation={false} + readonly={false} + errors={[]} + fieldType={FieldType.markdown} + fieldName={fieldName} + /> + + ) : ( + {get(node, fieldName)} + )} + + ); +} diff --git a/designer/client/src/components/graph/node-modal/NodeTypeDetailsContent.tsx b/designer/client/src/components/graph/node-modal/NodeTypeDetailsContent.tsx index dd2f2609628..abf135e1576 100644 --- a/designer/client/src/components/graph/node-modal/NodeTypeDetailsContent.tsx +++ b/designer/client/src/components/graph/node-modal/NodeTypeDetailsContent.tsx @@ -1,7 +1,20 @@ -import { Edge, NodeType, NodeValidationError, PropertiesType } from "../../../types"; +import { cloneDeep, isEqual, set } from "lodash"; import React, { SetStateAction, useCallback, useEffect, useMemo } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { nodeDetailsClosed, nodeDetailsOpened, validateNodeData } from "../../../actions/nk"; import { getProcessDefinitionData } from "../../../reducers/selectors/settings"; +import { Edge, NodeType, NodeValidationError, PropertiesType } from "../../../types"; +import NodeUtils from "../NodeUtils"; +import { CustomNode } from "./customNode"; +import { EnricherProcessor } from "./enricherProcessor"; +import { ParamFieldLabel } from "./FieldLabel"; +import { Filter } from "./filter"; +import FragmentInputDefinition from "./fragment-input-definition/FragmentInputDefinition"; +import { FragmentInputParameter } from "./fragment-input-definition/item"; +import { FragmentInput } from "./fragmentInput"; +import FragmentOutputDefinition from "./FragmentOutputDefinition"; +import { JoinNode } from "./joinNode"; +import { NodeDetailsFallback } from "./NodeDetailsContent/NodeDetailsFallback"; import { getDynamicParameterDefinitions, getFindAvailableBranchVariables, @@ -9,48 +22,29 @@ import { getProcessName, getProcessProperties, } from "./NodeDetailsContent/selectors"; -import { adjustParameters } from "./ParametersUtils"; import { generateUUIDs } from "./nodeUtils"; -import { ParamFieldLabel } from "./FieldLabel"; -import { cloneDeep, isEqual, set } from "lodash"; -import { nodeDetailsClosed, nodeDetailsOpened, validateNodeData } from "../../../actions/nk"; -import NodeUtils from "../NodeUtils"; -import { Source } from "./source"; +import { adjustParameters } from "./ParametersUtils"; +import { Properties } from "./properties"; import { Sink } from "./sink"; -import FragmentInputDefinition from "./fragment-input-definition/FragmentInputDefinition"; -import FragmentOutputDefinition from "./FragmentOutputDefinition"; -import { Filter } from "./filter"; -import { EnricherProcessor } from "./enricherProcessor"; -import { FragmentInput } from "./fragmentInput"; -import { JoinNode } from "./joinNode"; -import { VariableBuilder } from "./variableBuilder"; -import { Switch } from "./switch"; +import { Source } from "./source"; import { Split } from "./split"; -import { Properties } from "./properties"; -import { NodeDetailsFallback } from "./NodeDetailsContent/NodeDetailsFallback"; +import { Switch } from "./switch"; import Variable from "./Variable"; -import { FragmentInputParameter } from "./fragment-input-definition/item"; -import { CustomNode } from "./customNode"; +import { VariableBuilder } from "./variableBuilder"; type ArrayElement = A extends readonly (infer E)[] ? E : never; -interface NodeTypeDetailsContentProps { +export type NodeTypeDetailsContentProps = { node: NodeType; edges?: Edge[]; onChange?: (node: SetStateAction, edges?: SetStateAction) => void; showValidation?: boolean; showSwitch?: boolean; errors: NodeValidationError[]; -} +}; -export function NodeTypeDetailsContent({ - node, - edges, - onChange, - errors, - showValidation, - showSwitch, -}: NodeTypeDetailsContentProps): JSX.Element { +export function useNodeTypeDetailsContentLogic(props: NodeTypeDetailsContentProps) { + const { onChange, node, edges, showValidation } = props; const dispatch = useDispatch(); const isEditMode = !!onChange; @@ -153,6 +147,40 @@ export function NodeTypeDetailsContent({ }); }, [adjustNode, setEditedNode]); + return { + ...props, + isEditMode, + processDefinitionData, + findAvailableVariables, + variableTypes, + setEditedEdges, + parameterDefinitions, + renderFieldLabel, + removeElement, + addElement, + setProperty, + }; +} + +export function NodeTypeDetailsContent(props: NodeTypeDetailsContentProps): JSX.Element { + const { + isEditMode, + processDefinitionData, + findAvailableVariables, + variableTypes, + setEditedEdges, + parameterDefinitions, + renderFieldLabel, + removeElement, + addElement, + setProperty, + node, + edges, + errors, + showValidation, + showSwitch, + } = useNodeTypeDetailsContentLogic(props); + switch (NodeUtils.nodeType(node)) { case "Source": return ( diff --git a/designer/client/src/components/graph/node-modal/edge/EdgeDetailsContent.tsx b/designer/client/src/components/graph/node-modal/edge/EdgeDetailsContent.tsx index b11c9141570..0f00d4c8b65 100644 --- a/designer/client/src/components/graph/node-modal/edge/EdgeDetailsContent.tsx +++ b/designer/client/src/components/graph/node-modal/edge/EdgeDetailsContent.tsx @@ -1,10 +1,9 @@ +import { FormLabel } from "@mui/material"; import React, { useCallback } from "react"; import { Edge, EdgeKind, EdgeType } from "../../../../types"; import BaseModalContent from "../BaseModalContent"; import EditableEditor from "../editors/EditableEditor"; import { useDiffMark } from "../PathsToMark"; -import { getValidationErrorsForField } from "../editors/Validators"; -import { FormLabel } from "@mui/material"; interface Props { edge: Edge; @@ -14,12 +13,17 @@ interface Props { showValidation?: boolean; showSwitch?: boolean; variableTypes; - edgeErrors?; } -export default function EdgeDetailsContent(props: Props): JSX.Element | null { - const { edge, edgeErrors, readOnly, changeEdgeTypeCondition, showValidation, showSwitch, changeEdgeTypeValue, variableTypes } = props; - +export default function EdgeDetailsContent({ + edge, + readOnly, + changeEdgeTypeValue, + changeEdgeTypeCondition, + showValidation, + showSwitch, + variableTypes, +}: Props): JSX.Element | null { const [isMarked] = useDiffMark(); const renderFieldLabel = useCallback((label) => {label}, []); @@ -28,7 +32,7 @@ export default function EdgeDetailsContent(props: Props): JSX.Element | null { return ( ); diff --git a/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx b/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx index 99e1f5ff937..374e97835df 100644 --- a/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx +++ b/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx @@ -1,9 +1,11 @@ /* eslint-disable i18next/no-literal-string */ -import React, { ForwardedRef, forwardRef, useMemo } from "react"; +import { throttle } from "lodash"; +import React, { ForwardedRef, forwardRef, useEffect, useMemo, useRef } from "react"; import ReactAce from "react-ace/lib/ace"; +import { useMergeRefs } from "rooks"; import { useUserSettings } from "../../../../../common/userSettings"; -import AceWrapper, { AceKeyCommand, AceWrapperProps } from "./AceWrapper"; import { UserSettings } from "../../../../../reducers/userSettings"; +import AceWrapper, { AceKeyCommand, AceWrapperProps } from "./AceWrapper"; export default forwardRef(function AceWithSettings( props: Omit, @@ -34,10 +36,38 @@ export default forwardRef(function AceWithSettings( [toggleSettings, showLinesName, noWrapName], ); + const editorRef = useRef(); + useEffect(() => { + const editor = editorRef.current?.editor; + const selection = editor?.session.selection; + + // before setting cursor position ensure all position calculations are actual + const prepare = () => editor?.renderer.updateFull(true); + + const scrollToView = throttle( + () => { + document.activeElement.scrollIntoView({ block: "nearest", inline: "nearest" }); + }, + 150, + { leading: false }, + ); + + editor?.addEventListener("mousedown", prepare); + editor?.addEventListener("mouseup", scrollToView); + selection?.on("changeCursor", scrollToView); + return () => { + editor?.removeEventListener("mousedown", prepare); + editor?.removeEventListener("mouseup", scrollToView); + selection?.off("changeCursor", scrollToView); + }; + }, []); + + const mergedRefs = useMergeRefs(editorRef, ref); + return ( - !readOnly - ? { - title: t("dialog.button.apply", "apply"), - action: performNodeEdit, - disabled: !editedNode.id?.length, - } - : null, - [editedNode.id?.length, performNodeEdit, readOnly, t], - ); - - const openFragmentButtonData: WindowButtonProps | null = useMemo( - () => - NodeUtils.nodeIsFragment(editedNode) - ? { - title: t("dialog.button.fragment.edit", "edit fragment"), - action: () => { - window.open(urljoin(BASE_PATH, visualizationUrl(editedNode.ref.id))); - }, - classname: "tertiary-button", - } - : null, - [editedNode, t], - ); - - const cancelButtonData = useMemo( - () => ({ title: t("dialog.button.cancel", "cancel"), action: props.close, classname: LoadingButtonTypes.secondaryButton }), - [props, t], - ); - useKey("Escape", (e) => { e.preventDefault(); if (!isInputTarget(e.composedPath().shift())) { @@ -98,17 +69,95 @@ export function NodeDetails(props: NodeDetailsProps): JSX.Element { } }); + const descriptionOnly = props.data.kind === WindowKind.viewDescription; + const [previewMode, setPreviewMode] = useState(true); + const touched = useMemo(() => node !== editedNode, [editedNode, node]); + + const applyButtonData = useMemo(() => { + if (readOnly || (descriptionOnly && previewMode && !touched)) return null; + return { + title: t("dialog.button.apply", "apply"), + action: performNodeEdit, + disabled: !editedNode.id?.length, + }; + }, [descriptionOnly, editedNode.id?.length, performNodeEdit, previewMode, readOnly, t, touched]); + + const openFragmentButtonData = useMemo(() => { + if (!NodeUtils.nodeIsFragment(editedNode) || descriptionOnly) return null; + return { + title: t("dialog.button.fragment.edit", "edit fragment"), + action: () => { + window.open(urljoin(BASE_PATH, visualizationUrl(editedNode.ref.id))); + }, + classname: "tertiary-button", + }; + }, [descriptionOnly, editedNode, t]); + + const cancelButtonData = useMemo(() => { + if (descriptionOnly && previewMode && !touched) return null; + return { + title: t("dialog.button.cancel", "cancel"), + action: descriptionOnly + ? () => { + setEditedNode(node); + setPreviewMode(true); + } + : props.close, + classname: LoadingButtonTypes.secondaryButton, + }; + }, [descriptionOnly, node, previewMode, props.close, t, touched]); + + const previewButtonData = useMemo(() => { + if (!descriptionOnly || !touched) return null; + return { + title: previewMode ? "edit" : "preview", + action: () => setPreviewMode((v) => !v), + className: LoadingButtonTypes.tertiaryButton, + }; + }, [descriptionOnly, previewMode, touched]); + const buttons: WindowButtonProps[] = useMemo( - () => [openFragmentButtonData, cancelButtonData, applyButtonData].filter(Boolean), - [applyButtonData, cancelButtonData, openFragmentButtonData], + () => [openFragmentButtonData, previewButtonData, cancelButtonData, applyButtonData].filter(Boolean), + [applyButtonData, cancelButtonData, openFragmentButtonData, previewButtonData], ); useEffect(() => { - mergeQuery(parseWindowsQueryParams({ nodeId: node.id })); - return () => { - mergeQuery(parseWindowsQueryParams({}, { nodeId: node.id })); - }; - }, [node.id]); + if (!descriptionOnly) { + mergeQuery(parseWindowsQueryParams({ nodeId: node.id })); + return () => { + mergeQuery(parseWindowsQueryParams({}, { nodeId: node.id })); + }; + } + }, [descriptionOnly, node.id]); + + const componentsOverride = useMemo>(() => { + if (!descriptionOnly) return {}; + + const HeaderTitle = () =>
; + + if (touched || !previewMode) { + return { HeaderTitle }; + } + + const Header = (props) => ; + const HeaderButtonZoom = (props) => ( + <> + setPreviewMode(false)} name="edit"> + + + + + ); + + return { Header, HeaderTitle, HeaderButtonZoom }; + }, [touched, descriptionOnly, previewMode]); //no process? no nodes? no window contents! no errors for whole tree! if (!scenarioFromGlobalStore?.scenarioGraph.nodes) { @@ -125,9 +174,14 @@ export function NodeDetails(props: NodeDetailsProps): JSX.Element { classnames={{ content: css({ minHeight: "100%", display: "flex", ">div": { flex: 1 }, position: "relative" }), }} + components={componentsOverride} > - + {descriptionOnly ? ( + + ) : ( + + )} ); diff --git a/designer/client/src/components/graph/node-modal/node/StyledHeader.tsx b/designer/client/src/components/graph/node-modal/node/StyledHeader.tsx index 21896e0c453..905117b8484 100644 --- a/designer/client/src/components/graph/node-modal/node/StyledHeader.tsx +++ b/designer/client/src/components/graph/node-modal/node/StyledHeader.tsx @@ -1,8 +1,8 @@ import { styled } from "@mui/material"; +import { getLuminance } from "@mui/system/colorManipulator"; import { DefaultComponents as Window } from "@touk/window-manager"; import { blendDarken, blendLighten } from "../../../../containers/theme/helpers"; -import { getLuminance } from "@mui/system/colorManipulator"; export const StyledHeader = styled(Window.Header)(({ isMaximized, isStatic, theme }) => { const draggable = !isMaximized && !isStatic; @@ -17,3 +17,12 @@ export const StyledHeader = styled(Window.Header)(({ isMaximized, isStatic, them }, }; }); + +export const StyledContent = styled(Window.Content)(({ theme }) => { + return { + "body :has(>&)": { + scrollPadding: theme.spacing(3.5), + scrollPaddingTop: theme.spacing(6), + }, + }; +}); diff --git a/designer/client/src/components/toolbars/scenarioActions/buttons/PropertiesButton.tsx b/designer/client/src/components/toolbars/scenarioActions/buttons/PropertiesButton.tsx index e04eee88f86..48002a8b4ca 100644 --- a/designer/client/src/components/toolbars/scenarioActions/buttons/PropertiesButton.tsx +++ b/designer/client/src/components/toolbars/scenarioActions/buttons/PropertiesButton.tsx @@ -1,25 +1,33 @@ +import { WindowType } from "@touk/window-manager"; import React, { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; import Icon from "../../../../assets/img/toolbarButtons/properties.svg"; -import { getProcessUnsavedNewName, hasError, hasPropertiesErrors, getScenario } from "../../../../reducers/selectors/graph"; +import { getProcessUnsavedNewName, getScenario, hasError, hasPropertiesErrors } from "../../../../reducers/selectors/graph"; import { useWindows } from "../../../../windowManager"; +import { NodeViewMode } from "../../../../windowManager/useWindows"; import NodeUtils from "../../../graph/NodeUtils"; import { ToolbarButton } from "../../../toolbarComponents/toolbarButtons"; import { ToolbarButtonProps } from "../../types"; -function PropertiesButton(props: ToolbarButtonProps): JSX.Element { - const { t } = useTranslation(); +export function useOpenProperties() { const { openNodeWindow } = useWindows(); - const { disabled, type } = props; const scenario = useSelector(getScenario); const name = useSelector(getProcessUnsavedNewName); + const processProperties = useMemo(() => NodeUtils.getProcessPropertiesNode(scenario, name), [name, scenario]); + return useCallback( + (mode?: NodeViewMode, layout?: WindowType["layoutData"]) => openNodeWindow(processProperties, scenario, mode, layout), + [openNodeWindow, processProperties, scenario], + ); +} + +function PropertiesButton(props: ToolbarButtonProps): JSX.Element { + const { t } = useTranslation(); + const { disabled, type } = props; const propertiesErrors = useSelector(hasPropertiesErrors); const errors = useSelector(hasError); - const processProperties = useMemo(() => NodeUtils.getProcessPropertiesNode(scenario, name), [name, scenario]); - - const onClick = useCallback(() => openNodeWindow(processProperties, scenario), [openNodeWindow, processProperties, scenario]); + const openProperties = useOpenProperties(); return ( } disabled={disabled} - onClick={onClick} + onClick={() => openProperties()} type={type} /> ); diff --git a/designer/client/src/containers/DescriptionView.tsx b/designer/client/src/containers/DescriptionView.tsx new file mode 100644 index 00000000000..22f5a2adc39 --- /dev/null +++ b/designer/client/src/containers/DescriptionView.tsx @@ -0,0 +1,6 @@ +import React from "react"; +import { MarkdownStyled } from "../components/graph/node-modal/MarkdownStyled"; + +export const DescriptionView = ({ children }: { children: string }) => { + return {children}; +}; diff --git a/designer/client/src/containers/NussknackerApp.tsx b/designer/client/src/containers/NussknackerApp.tsx index c13c7859a7e..c1d269c42b4 100644 --- a/designer/client/src/containers/NussknackerApp.tsx +++ b/designer/client/src/containers/NussknackerApp.tsx @@ -1,17 +1,17 @@ import { css } from "@emotion/css"; +import { isEmpty } from "lodash"; import React from "react"; import { useSelector } from "react-redux"; +import { Outlet } from "react-router-dom"; import { MenuBar } from "../components/MenuBar"; import { VersionInfo } from "../components/versionInfo"; import { getLoggedUser } from "../reducers/selectors/settings"; -import { isEmpty } from "lodash"; -import { Outlet } from "react-router-dom"; -import { Notifications } from "./Notifications"; -import { useAnonymousStatistics } from "./useAnonymousStatistics"; import { WindowManager } from "../windowManager"; import { ConnectionErrorProvider } from "./connectionErrorProvider"; import { useRegisterTrackingEvents } from "./event-tracking"; import { useErrorRegister } from "./event-tracking/use-error-register"; +import { Notifications } from "./Notifications"; +import { useAnonymousStatistics } from "./useAnonymousStatistics"; export function NussknackerApp() { const loggedUser = useSelector(getLoggedUser); @@ -30,6 +30,9 @@ export function NussknackerApp() { className={css({ flex: 1, display: "flex", + "& *": { + scrollPadding: 40, + }, })} >
{ const [description, showDescription] = useSelector(getScenarioDescription); - const [expanded, setExpanded] = useState(showDescription); - const toggle = () => setExpanded((v) => !v); + const openProperties = useOpenProperties(); + + const ref = useRef(); + + const openDescription = useCallback(() => { + const { top, left } = ref.current.getBoundingClientRect(); + openProperties(NodeViewMode.description, { top, left, width: 600 }); + }, [openProperties]); + + useEffect(() => { + if (showDescription) { + openDescription(); + } + }, []); + const { t } = useTranslation(); const title = t("graph.description.toggle", "toggle description view"); return ( - <> - - - - - - - t.breakpoints.values.md * 0.9, - background: (t) => alpha(t.palette.background.paper, 0.75), - backdropFilter: "blur(25px)", - margin: 0, - padding: 1, - display: "flex", - flexGrow: 0, - flexShrink: 1, - }} - > - - - - - {description} - - - - + + + ); }; diff --git a/designer/client/src/windowManager/ContentGetter.tsx b/designer/client/src/windowManager/ContentGetter.tsx index 8e05e736781..645c4ac01b9 100644 --- a/designer/client/src/windowManager/ContentGetter.tsx +++ b/designer/client/src/windowManager/ContentGetter.tsx @@ -1,12 +1,12 @@ +import loadable from "@loadable/component"; import { WindowContentProps } from "@touk/window-manager"; import React from "react"; +import FrameDialog from "../components/FrameDialog"; +import LoaderSpinner from "../components/spinner/Spinner"; import { Debug } from "../containers/Debug"; +import { NuThemeProvider } from "../containers/theme/nuThemeProvider"; import { WindowContent } from "./WindowContent"; import { WindowKind } from "./WindowKind"; -import loadable from "@loadable/component"; -import LoaderSpinner from "../components/spinner/Spinner"; -import FrameDialog from "../components/FrameDialog"; -import { NuThemeProvider } from "../containers/theme/nuThemeProvider"; const AddProcessDialog = loadable(() => import("../components/AddProcessDialog"), { fallback: }); const NodeDetails = loadable(() => import("../components/graph/node-modal/node/NodeDetails"), { @@ -72,14 +72,14 @@ const contentGetter: React.FC> = (props) => { case WindowKind.inform: return ; case WindowKind.editNode: + case WindowKind.viewDescription: return ; case WindowKind.viewNode: return ; case WindowKind.survey: return ; - case WindowKind.scenarioDetails: { + case WindowKind.scenarioDetails: return ; - } default: return ( diff --git a/designer/client/src/windowManager/WindowContent.tsx b/designer/client/src/windowManager/WindowContent.tsx index 21d04e323db..7d1663ecdf2 100644 --- a/designer/client/src/windowManager/WindowContent.tsx +++ b/designer/client/src/windowManager/WindowContent.tsx @@ -4,7 +4,7 @@ import React, { PropsWithChildren, ReactElement, useMemo } from "react"; import { LoadingButton } from "./LoadingButton"; import ErrorBoundary from "../components/common/ErrorBoundary"; import { useTheme } from "@mui/material"; -import { StyledHeader } from "../components/graph/node-modal/node/StyledHeader"; +import { StyledContent, StyledHeader } from "../components/graph/node-modal/node/StyledHeader"; import { IconModalHeader } from "../components/graph/node-modal/nodeDetails/NodeDetailsModalHeader"; type WindowContentProps = DefaultContentProps & @@ -29,6 +29,7 @@ export function WindowContent({ children, icon, subheader, ...props }: WindowCon const components = useMemo( () => ({ Header: StyledHeader, + Content: StyledContent, HeaderTitle: (headerProps) => , FooterButton: LoadingButton, ...props.components, diff --git a/designer/client/src/windowManager/WindowKind.tsx b/designer/client/src/windowManager/WindowKind.tsx index 43db56ffe35..fdf77461aa2 100644 --- a/designer/client/src/windowManager/WindowKind.tsx +++ b/designer/client/src/windowManager/WindowKind.tsx @@ -16,4 +16,5 @@ export enum WindowKind { genericAction, survey, scenarioDetails, + viewDescription, } diff --git a/designer/client/src/windowManager/useWindows.ts b/designer/client/src/windowManager/useWindows.ts index ba9bbef8e40..217898b1163 100644 --- a/designer/client/src/windowManager/useWindows.ts +++ b/designer/client/src/windowManager/useWindows.ts @@ -3,10 +3,27 @@ import { defaults } from "lodash"; import { useCallback, useMemo } from "react"; import { useUserSettings } from "../common/userSettings"; import { ConfirmDialogData } from "../components/modals/GenericConfirmDialog"; -import { NodeType } from "../types"; -import { WindowKind } from "./WindowKind"; import { InfoDialogData } from "../components/modals/GenericInfoDialog"; import { Scenario } from "../components/Process/types"; +import { NodeType } from "../types"; +import { WindowKind } from "./WindowKind"; + +export const NodeViewMode = { + edit: false, + readonly: true, + description: "description", +} as const; +export type NodeViewMode = (typeof NodeViewMode)[keyof typeof NodeViewMode]; + +function mapModeToKind(mode: NodeViewMode): WindowKind { + switch (mode) { + case NodeViewMode.readonly: + return WindowKind.viewNode; + case NodeViewMode.description: + return WindowKind.viewDescription; + } + return WindowKind.editNode; +} export function useWindows(parent?: WindowId) { const { open: _open, closeAll } = useWindowManager(parent); @@ -22,15 +39,17 @@ export function useWindows(parent?: WindowId) { ); const openNodeWindow = useCallback( - (node: NodeType, scenario: Scenario, readonly?: boolean) => - open({ + (node: NodeType, scenario: Scenario, viewMode: NodeViewMode = false, layoutData?: WindowType["layoutData"]) => { + return open({ id: node.id, title: node.id, isResizable: true, - kind: readonly ? WindowKind.viewNode : WindowKind.editNode, + kind: mapModeToKind(viewMode), meta: { node, scenario }, shouldCloseOnEsc: false, - }), + layoutData, + }); + }, [open], ); diff --git a/designer/client/webpack.config.ts b/designer/client/webpack.config.ts index 24874dc064d..b52d9e4a0a9 100644 --- a/designer/client/webpack.config.ts +++ b/designer/client/webpack.config.ts @@ -1,20 +1,20 @@ /* eslint-disable i18next/no-literal-string */ -import progressBar from "./progressBar.js"; -import path from "path"; -import webpack, { Configuration } from "webpack"; -import HtmlWebpackPlugin from "html-webpack-plugin"; -import HtmlWebpackHarddiskPlugin from "html-webpack-harddisk-plugin"; -import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; -import MomentLocalesPlugin from "moment-locales-webpack-plugin"; import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin"; import PreloadWebpackPlugin from "@vue/preload-webpack-plugin"; -import CopyPlugin from "copy-webpack-plugin"; import autoprefixer from "autoprefixer"; +import CopyPlugin from "copy-webpack-plugin"; +import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; +import HtmlWebpackHarddiskPlugin from "html-webpack-harddisk-plugin"; +import HtmlWebpackPlugin from "html-webpack-plugin"; +import MomentLocalesPlugin from "moment-locales-webpack-plugin"; +import path from "path"; import postcss_move_props_to_bg_image_query from "postcss-move-props-to-bg-image-query"; +import webpack, { Configuration } from "webpack"; import { withModuleFederationPlugins } from "./configs/withModuleFederationPlugins"; +import { dependencies } from "./package.json"; +import progressBar from "./progressBar.js"; import { hash, version } from "./version"; import "webpack-dev-server"; -import { dependencies } from "./package.json"; const isProd = process.env.NODE_ENV === "production"; const entry = { @@ -37,6 +37,8 @@ const outputPath = path.join(process.cwd(), "dist"); const mode = isProd ? "production" : "development"; const isBundleReport = process.env.NODE_ENV === "bundleReport"; +const PORT = process.env.PORT || 3000; + const config: Configuration = { mode: mode, performance: { @@ -80,7 +82,7 @@ const config: Configuration = { "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS", "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization", }, - port: 3000, + port: PORT, proxy: { "/api": { target: process.env.BACKEND_DOMAIN, @@ -125,7 +127,7 @@ const config: Configuration = { }, }, "/static": { - target: "http://localhost:3000", + target: `http://localhost:${PORT}`, changeOrigin: true, pathRewrite: { "^/static": "/",