diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fac959b6d..43f55d933 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -25,16 +25,12 @@ const App = ({ isUnibotics }: { isUnibotics: boolean }) => { const [forceSaveCurrent, setForcedSaveCurrent] = useState(false); const [currentProjectname, setCurrentProjectname] = useState(""); const [currentUniverseName, setCurrentUniverseName] = useState(""); - const [actionNodesData, setActionNodesData] = useState>( - {}, - ); const [saveCurrentDiagram, setSaveCurrentDiagram] = useState(false); const [updateFileExplorer, setUpdateFileExplorer] = useState(false); const [isErrorModalOpen, setErrorModalOpen] = useState(false); const [projectChanges, setProjectChanges] = useState(false); const [gazeboEnabled, setGazeboEnabled] = useState(false); const [manager, setManager] = useState(null); - const [diagramEditorReady, setDiagramEditorReady] = useState(false); const [showSim, setSimVisible] = useState(false); const [showTerminal, setTerminalVisible] = useState(false); @@ -190,9 +186,6 @@ const App = ({ isUnibotics }: { isUnibotics: boolean }) => { currentFilename={currentFilename} currentProjectname={currentProjectname} setProjectChanges={setProjectChanges} - actionNodesData={actionNodesData} - showAccentColor={"editorShowAccentColors"} - diagramEditorReady={diagramEditorReady} setAutosave={setAutosave} forceSaveCurrent={forceSaveCurrent} setForcedSaveCurrent={setForcedSaveCurrent} diff --git a/frontend/src/components/file_browser/FileBrowser.js b/frontend/src/components/file_browser/FileBrowser.js index 8493066ae..0d81b7f01 100644 --- a/frontend/src/components/file_browser/FileBrowser.js +++ b/frontend/src/components/file_browser/FileBrowser.js @@ -41,9 +41,6 @@ const FileBrowser = ({ currentFilename, currentProjectname, setProjectChanges, - actionNodesData, - showAccentColor, - diagramEditorReady, setAutosave, forceSaveCurrent, setForcedSaveCurrent, @@ -395,9 +392,6 @@ const FileBrowser = ({ currentFilename={currentFilename} currentProjectname={currentProjectname} setSelectedEntry={setSelectedEntry} - actionNodesData={actionNodesData} - showAccentColor={showAccentColor} - diagramEditorReady={diagramEditorReady} fileList={fileList} fetchFileList={fetchFileList} onDelete={handleDeleteModal} diff --git a/frontend/src/components/file_browser/file_explorer/FileExplorer.jsx b/frontend/src/components/file_browser/file_explorer/FileExplorer.jsx index 51d7cdc8b..de716d32e 100644 --- a/frontend/src/components/file_browser/file_explorer/FileExplorer.jsx +++ b/frontend/src/components/file_browser/file_explorer/FileExplorer.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import "./FileExplorer.css"; -import TreeNode from "./TreeNode.jsx"; +import TreeNode from "./TreeNode"; import MoreActionsMenu, { ContextMenuProps } from "./MoreActionsMenu.jsx"; const FileExplorer = ({ @@ -9,9 +9,6 @@ const FileExplorer = ({ currentFilename, currentProjectname, setSelectedEntry, - actionNodesData, - showAccentColor, - diagramEditorReady, fileList, fetchFileList, onDelete, @@ -65,9 +62,6 @@ const FileExplorer = ({ depth={0} parentGroup="" currentFilename={currentFilename} - showAccentColor={showAccentColor} - diagramEditorReady={diagramEditorReady} - actionNodesData={actionNodesData} handleFileClick={handleFileClick} handleFolderClick={handleFolderClick} menuProps={MenuProps} @@ -76,7 +70,6 @@ const FileExplorer = ({ {showMenu && ( (false); + const [group, setGroup] = useState(parentGroup); + const settings = React.useContext(OptionsContext); useEffect(() => { if (node.is_dir) { @@ -65,13 +81,12 @@ function TreeNode({ ); }} /> - {showAccentColor && diagramEditorReady && ( + {settings.editorShowAccentColors.value && (
)} @@ -84,9 +99,6 @@ function TreeNode({ depth={depth + 1} parentGroup={group} currentFilename={currentFilename} - showAccentColor={showAccentColor} - diagramEditorReady={diagramEditorReady} - actionNodesData={actionNodesData} handleFileClick={handleFileClick} handleFolderClick={handleFolderClick} menuProps={menuProps} diff --git a/frontend/src/components/helper/TreeEditorHelper.ts b/frontend/src/components/helper/TreeEditorHelper.ts index 6d52fc631..d50fba9e4 100644 --- a/frontend/src/components/helper/TreeEditorHelper.ts +++ b/frontend/src/components/helper/TreeEditorHelper.ts @@ -3,6 +3,7 @@ import { DiagramModel, LinkModel, NodeModel, + PortModel, ZoomCanvasAction, } from "@projectstorm/react-diagrams"; @@ -50,14 +51,111 @@ export const isActionNode = (nodeName: string) => { ].includes(nodeName); }; +export class ActionFrame { + public name: string; + private color: string; + private inputs: string[]; + private outputs: string[]; + + constructor( + name: string, + color: string, + inputs: string[], + outputs: string[] + ) { + this.name = name; + this.color = color; + this.inputs = inputs; + this.outputs = outputs; + } + + public changeColor(color: string) { + this.color = color; + } + + public getColor() : string { + return this.color; + } + + public addInput(name: string) { + if (!this.inputs.includes(name)) { + this.inputs.push(name); + } + } + + public addOutput(name: string) { + if (!this.outputs.includes(name)) { + this.outputs.push(name); + } + } + + public removeInput(name: string) { + if (this.inputs.includes(name)) { + this.inputs = this.inputs.filter((input) => input !== name); + } + } + + public removeOutput(name: string) { + if (this.outputs.includes(name)) { + this.outputs = this.outputs.filter((output) => output !== name); + } + } + + public getInputs() : string[] { + return this.inputs; + } + + public getOutputs() : string[] { + return this.outputs; + } +} + +var actionFrames: ActionFrame[] = []; + +export const getActionFrame = (name: string) => { + for (let index = 0; index < actionFrames.length; index++) { + const element = actionFrames[index]; + if (element.name === name) { + return element; + } + } + return undefined; +}; + +export const resetActionFrames = () => { + actionFrames = []; +}; + +export const addActionFrame = (name: string, color:string, ports:{ [s: string]: PortModel; } ) => { + if (getActionFrame(name) !== undefined) { + return; // Already exists + } + + var inputs: string[] = []; + var outputs: string[] = []; + + Object.values(ports).forEach((port) => { + if (port instanceof InputPortModel) { + inputs.push(port.getName()) + } else if (port instanceof OutputPortModel) { + outputs.push(port.getName()) + } + }); + + var newActionFrame = new ActionFrame(name, color, inputs, outputs); + + actionFrames.push(newActionFrame); +}; + export const addPort = ( portName: string, + action: ActionFrame | undefined, node: BasicNodeModel, type: ActionNodePortType, engine: DiagramEngine, model: DiagramModel, diagramEditedCallback: React.Dispatch>, - updateJsonState: Function, + updateJsonState: Function ) => { // Check that the user didn't cancel if (!node || !portName) { @@ -66,8 +164,14 @@ export const addPort = ( if (type === ActionNodePortType.Input) { node.addInputPort(portName); + if (action) { + action.addInput(portName); + } } else { node.addOutputPort(portName); + if (action) { + action.addOutput(portName); + } } model.getNodes().forEach((oldNode: NodeModel) => { @@ -98,7 +202,7 @@ export const addPort = ( const deletePortLink = ( model: DiagramModel, portName: string, - node: BasicNodeModel, + node: BasicNodeModel ) => { // var link: LinkModel | undefined; var link: any; @@ -114,11 +218,12 @@ const deletePortLink = ( export const removePort = ( port: OutputPortModel | InputPortModel, + action: ActionFrame | undefined, node: BasicNodeModel, engine: DiagramEngine, model: DiagramModel, diagramEditedCallback: React.Dispatch>, - updateJsonState: Function, + updateJsonState: Function ) => { //TODO: type should be an enum // Check that the user didn't cancel @@ -132,8 +237,14 @@ export const removePort = ( if (port instanceof InputPortModel) { node.removeInputPort(port); + if (action) { + action.removeInput(portName); + } } else { node.removeOutputPort(port); + if (action) { + action.removeOutput(portName); + } } model.getNodes().forEach((oldNode: NodeModel) => { @@ -167,13 +278,14 @@ export const removePort = ( export const changeColorNode = ( rgb: [number, number, number], + action: ActionFrame | undefined, node: BasicNodeModel, engine: DiagramEngine, model: DiagramModel, diagramEditedCallback: React.Dispatch< React.SetStateAction > = () => {}, - updateJsonState: Function = () => {}, + updateJsonState: Function = () => {} ) => { node.setColor( "rgb(" + @@ -182,9 +294,21 @@ export const changeColorNode = ( Math.round(rgb[1]) + "," + Math.round(rgb[2]) + - ")", + ")" ); + if (action) { + action.changeColor( + "rgb(" + + Math.round(rgb[0]) + + "," + + Math.round(rgb[1]) + + "," + + Math.round(rgb[2]) + + ")" + ); + } + model.getNodes().forEach((oldNode: NodeModel) => { var convNode; var name; @@ -212,7 +336,7 @@ export const changeColorNode = ( export const configureEngine = ( engine: React.MutableRefObject, basicNodeCallback: Function | null = null, - tagNodeCallback: Function | null = null, + tagNodeCallback: Function | null = null ) => { console.log("Configuring engine!"); // Register factories @@ -225,32 +349,32 @@ export const configureEngine = ( engine.current .getPortFactories() .registerFactory( - new SimplePortFactory("children", (config) => new ChildrenPortModel()), + new SimplePortFactory("children", (config) => new ChildrenPortModel()) ); engine.current .getPortFactories() .registerFactory( - new SimplePortFactory("parent", (config) => new ParentPortModel()), + new SimplePortFactory("parent", (config) => new ParentPortModel()) ); engine.current .getPortFactories() .registerFactory( - new SimplePortFactory("output", (config) => new OutputPortModel("")), + new SimplePortFactory("output", (config) => new OutputPortModel("")) ); engine.current .getPortFactories() .registerFactory( - new SimplePortFactory("input", (config) => new InputPortModel("")), + new SimplePortFactory("input", (config) => new InputPortModel("")) ); engine.current .getPortFactories() .registerFactory( - new SimplePortFactory("tag output", (config) => new TagOutputPortModel()), + new SimplePortFactory("tag output", (config) => new TagOutputPortModel()) ); engine.current .getPortFactories() .registerFactory( - new SimplePortFactory("tag input", (config) => new TagInputPortModel()), + new SimplePortFactory("tag input", (config) => new TagInputPortModel()) ); // Disable loose links @@ -267,7 +391,7 @@ export const configureEngine = ( export const findSubtree = ( baseTree: any, subTree: string, - oldIndex: number = -1, + oldIndex: number = -1 ): number[] | undefined => { var path: number[] = []; var nodeChilds; diff --git a/frontend/src/components/options/Options.tsx b/frontend/src/components/options/Options.tsx index 1f4daf768..ff3d6c4fd 100644 --- a/frontend/src/components/options/Options.tsx +++ b/frontend/src/components/options/Options.tsx @@ -3,8 +3,8 @@ import { createContext, useState } from "react"; const OptionsContext = createContext({ editorShowAccentColors: { setter: () => {}, - value: true, - default_value: true, + value: false, + default_value: false, }, theme: { setter: () => {}, value: "dark", default_value: "dark" }, btOrder: { @@ -30,7 +30,7 @@ export interface SettingsData { const OptionsProvider = ({ children }: { children: any }) => { // TODO: try to not repeat the default values - const [editorShowAccentColors, setEditorShowAccentColors] = useState(true); + const [editorShowAccentColors, setEditorShowAccentColors] = useState(false); const [theme, setTheme] = useState("dark"); const [btOrder, setBtOrder] = useState("bottom-to-top"); @@ -39,7 +39,7 @@ const OptionsProvider = ({ children }: { children: any }) => { editorShowAccentColors: { setter: setEditorShowAccentColors, value: editorShowAccentColors, - default_value: true, + default_value: false, }, theme: { setter: setTheme, value: theme, default_value: "dark" }, btOrder: { diff --git a/frontend/src/components/settings_popup/SettingsModal.jsx b/frontend/src/components/settings_popup/SettingsModal.jsx index 8d92ebd77..25005aff6 100644 --- a/frontend/src/components/settings_popup/SettingsModal.jsx +++ b/frontend/src/components/settings_popup/SettingsModal.jsx @@ -17,15 +17,9 @@ import { OptionsContext } from "../options/Options"; import { saveProjectConfig } from "./../../api_helper/TreeWrapper"; const SettingsModal = ({ onSubmit, isOpen, onClose, currentProjectname }) => { - const [color, setColor] = useColor("rgb(128 0 128)"); - const [open, setOpen] = useState(false); - + // const [color, setColor] = useColor("rgb(128 0 128)"); const settings = React.useContext(OptionsContext); - useEffect(() => { - // Load settings - }, [isOpen]); - // useEffect(() => { // console.log("rgb("+Math.round(color.rgb.r)+","+Math.round(color.rgb.g)+","+Math.round(color.rgb.b)+")") // document.documentElement.style.setProperty("--header", "rgb("+Math.round(color.rgb.r)+","+Math.round(color.rgb.g)+","+Math.round(color.rgb.b)+")"); diff --git a/frontend/src/components/tree_editor/DiagramVisualizer.tsx b/frontend/src/components/tree_editor/DiagramVisualizer.tsx index 534863a2a..88e1c3a61 100644 --- a/frontend/src/components/tree_editor/DiagramVisualizer.tsx +++ b/frontend/src/components/tree_editor/DiagramVisualizer.tsx @@ -99,7 +99,7 @@ const setStatusNode = ( } if (node) { - changeColorNode(rgb, node, engine, model); + changeColorNode(rgb, undefined, node, engine, model); } }; diff --git a/frontend/src/components/tree_editor/TreeEditor.tsx b/frontend/src/components/tree_editor/TreeEditor.tsx index c72ca37ce..b0505cd79 100644 --- a/frontend/src/components/tree_editor/TreeEditor.tsx +++ b/frontend/src/components/tree_editor/TreeEditor.tsx @@ -8,6 +8,7 @@ import createEngine, { DiagramModel, DiagramModelGenerics, NodeModel, + PortModel, ZoomCanvasAction, } from "@projectstorm/react-diagrams"; import { CanvasWidget } from "@projectstorm/react-canvas-core"; @@ -25,6 +26,10 @@ import { configureEngine, isActionNode, TreeViewType, + ActionFrame, + resetActionFrames, + addActionFrame, + getActionFrame, } from "../helper/TreeEditorHelper"; import NodeMenu from "./NodeMenu"; @@ -88,6 +93,10 @@ const TreeEditor = memo( setBtOrder(settings.btOrder.value); }, [settings.btOrder.value]); + useEffect(() => { + resetActionFrames(); + }, [projectName]); + const updateJsonState = () => { if (modalModel) { setResultJson(modalModel.serialize()); @@ -283,21 +292,27 @@ const DiagramEditor = memo( else if (nodeName === "Repeat") node.addInputPort("num_cycles"); else if (nodeName === "Delay") node.addInputPort("delay_ms"); - model.current.getNodes().forEach((oldNode: NodeModel) => { - //TODO: for the tags, this will never be called. Maybe have a common type - if (oldNode instanceof BasicNodeModel) { - var convNode = oldNode as BasicNodeModel; - if (convNode.getName() === node.getName() && node !== convNode) { - node.setColor(convNode.getColor()); - Object.values(convNode.getPorts()).forEach((element) => { - if (element instanceof InputPortModel) { - node.addInputPort(element.getName()); - } else if (element instanceof OutputPortModel) { - node.addOutputPort(element.getName()); - } - }); - } + var actionFrame = getActionFrame(nodeName); + + if (!(node instanceof BasicNodeModel)) { + return; + } + + if (actionFrame === undefined) { + if (isActionNode(nodeName) && !node.getIsSubtree()) { + addActionFrame(nodeName, node.getColor(), node.getPorts()); } + return; + } + + node.setColor(actionFrame.getColor()); + + actionFrame.getInputs().forEach((input) => { + node.addInputPort(input); + }); + + actionFrame.getOutputs().forEach((output) => { + node.addInputPort(output); }); }; @@ -544,6 +559,13 @@ const DiagramEditor = memo( attachPositionListener(node); attachClickListener(node); node.setSelected(false); + if ( + node instanceof BasicNodeModel && + isActionNode(node.getName()) && + !node.getIsSubtree() + ) { + addActionFrame(node.getName(), node.getColor(), node.getPorts()); + } }); setModalModel(model.current); diff --git a/frontend/src/components/tree_editor/modals/EditActionModal.tsx b/frontend/src/components/tree_editor/modals/EditActionModal.tsx index cf0e80776..93a981e64 100644 --- a/frontend/src/components/tree_editor/modals/EditActionModal.tsx +++ b/frontend/src/components/tree_editor/modals/EditActionModal.tsx @@ -8,6 +8,8 @@ import { removePort, ActionNodePortType, changeColorNode, + ActionFrame, + getActionFrame, } from "../../helper/TreeEditorHelper"; import { rgbToLuminance } from "../../helper/colorHelper"; @@ -51,6 +53,7 @@ const EditActionModal = ({ const [outputName, setOutputName] = React.useState(false); const [allowCreation, setAllowCreation] = React.useState(false); const [formState, setFormState] = useState(initialEditActionModalData); + const [update, setUpdate] = React.useState(false); const handleInputChange = (event: any) => { const { name, value } = event.target; @@ -82,6 +85,12 @@ const EditActionModal = ({ } }, [isOpen]); + useEffect(() => { + if (update) { + setUpdate(false); + } + }, [update]); + useEffect(() => { if (currentActionNode && color) { var rgb: [number, number, number] = [ @@ -89,8 +98,12 @@ const EditActionModal = ({ color.rgb["g"], color.rgb["b"], ]; + + var actionFrame = getActionFrame(currentActionNode.getName()); + changeColorNode( rgb, + actionFrame, currentActionNode, engine, model, @@ -167,8 +180,11 @@ const EditActionModal = ({ const addInput = () => { //TODO: Maybe display some error message when the name is invalid if (isInputNameValid(formState["newInputName"])) { + var actionFrame = getActionFrame(currentActionNode.getName()); + addPort( formState["newInputName"], + actionFrame, currentActionNode, ActionNodePortType.Input, engine, @@ -184,8 +200,11 @@ const EditActionModal = ({ const addOutput = () => { //TODO: Maybe display some error message when the name is invalid if (isOutputNameValid(formState["newOutputName"])) { + var actionFrame = getActionFrame(currentActionNode.getName()); + addPort( formState["newOutputName"], + actionFrame, currentActionNode, ActionNodePortType.Output, engine, @@ -198,6 +217,38 @@ const EditActionModal = ({ reRender(); }; + const removeInput = (port: InputPortModel) => { + var actionFrame = getActionFrame(currentActionNode.getName()); + + removePort( + port, + actionFrame, + currentActionNode, + engine, + model, + setDiagramEdited, + updateJsonState, + ); + + setUpdate(true); + }; + + const removeOutput = (port: OutputPortModel) => { + var actionFrame = getActionFrame(currentActionNode.getName()); + + removePort( + port, + actionFrame, + currentActionNode, + engine, + model, + setDiagramEdited, + updateJsonState, + ); + + setUpdate(true); + }; + const cancelCreation = () => { setInputName(false); setOutputName(false); @@ -276,15 +327,7 @@ const EditActionModal = ({ }} title="Delete" onClick={() => { - removePort( - port[1] as InputPortModel, - currentActionNode, - engine, - model, - setDiagramEdited, - updateJsonState, - ); - reRender(); + removeInput(port[1] as InputPortModel); }} > { - removePort( - port[1] as OutputPortModel, - currentActionNode, - engine, - model, - setDiagramEdited, - updateJsonState, - ); - reRender(); + removeOutput(port[1] as OutputPortModel); }} >