import { useEffect, useState, useRef, useMemo } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { debounce, Dictionary, omit } from 'lodash'; import YAML from "yaml"; import { PlusIcon } from "@heroicons/react/solid"; import { IProjectPayload, IClientNodeItem, IServiceNodePosition } from "../../types"; import eventBus from "../../events/eventBus"; import { useProject, useUpdateProject } from "../../hooks/useProject"; import useWindowDimensions from "../../hooks/useWindowDimensions"; import { projectHttpCreate } from "../../services/project"; import { flattenGraphData } from "../../utils/generators"; import { nodeLibraries } from "../../utils/data/libraries"; import { getClientNodeItem, flattenLibraries, ensure, getClientNodesAndConnections, getMatchingSetIndex } from "../../utils"; import { checkHttpStatus } from "../../services/helpers"; import { generateHttp } from "../../services/generate"; import { Canvas } from "../Canvas"; import Spinner from "../Spinner"; import ModalConfirmDelete from "../Modal/Service/ConfirmDelete"; import ModalServiceCreate from "../Modal/Service/Create"; import ModalServiceEdit from "../Modal/Service/Edit"; import CodeEditor from "../CodeEditor"; interface IProjectProps {} export default function Project(props: IProjectProps) { const navigate = useNavigate(); const { uuid } = useParams<{ uuid: string }>(); const { height } = useWindowDimensions(); const { data, error, isFetching } = useProject(uuid); const mutation = useUpdateProject(uuid); const stateNodesRef = useRef>(); const stateConnectionsRef = useRef<[[string, string]] | []>(); const [generatedCode, setGeneratedCode] = useState(); const [formattedCode, setFormattedCode] = useState(""); const [showModalCreateService, setShowModalCreateService] = useState(false); const [showVolumesModal, setShowVolumesModal] = useState(false); const [showNetworksModal, setShowNetworksModal] = useState(false); const [nodeForEdit, setNodeForEdit] = useState(null); const [nodeForDelete, setNodeForDelete] = useState(null); const [language, setLanguage] = useState("yaml"); const [copyText, setCopyText] = useState("Copy"); const [nodes, setNodes] = useState({}); const [connections, setConnections] = useState<[[string, string]] | []>([]); const [projectName, setProjectName] = useState("Untitled"); const [canvasPosition, setCanvasPosition] = useState({top: 0, left: 0, scale: 1}); stateNodesRef.current = nodes; stateConnectionsRef.current = connections; const handleNameChange = (e: any) => { setProjectName(e.target.value); } const createProject = (payload: IProjectPayload) => { projectHttpCreate(JSON.stringify(payload)) .then(checkHttpStatus) .then(data => { navigate(`/projects/${data.uuid}`); }) .catch(err => {}) .finally(() => { }) } const onNodeUpdate = (positionData: IServiceNodePosition) => { if (stateNodesRef.current) { const node = { ...stateNodesRef.current[positionData.key], ...positionData }; setNodes({ ...stateNodesRef.current, [positionData.key]: node }); } } const onSave = () => { const payload: IProjectPayload = { name: projectName, data: { canvas: { position: canvasPosition, nodes: nodes, connections: connections }, configs: [], networks: [], secrets: [], services: nodes, version: 3, volumes: [] } } if (uuid) { mutation.mutate(payload); } else { createProject(payload); } } const setViewHeight = () => { let vh = window.innerHeight * 0.01; document.documentElement.style.setProperty("--vh", `${vh}px`); } const copy = () => { navigator.clipboard.writeText(formattedCode); setCopyText("Copied"); setTimeout(() => { setCopyText("Copy"); }, 300); } useEffect(() => { if (!data) { return; } console.log(data); const canvasData = JSON.parse(data.data); const nodesAsList = Object.keys(canvasData.canvas.nodes).map(k => canvasData.canvas.nodes[k]); const clientNodeItems = getClientNodesAndConnections(nodesAsList, nodeLibraries); setProjectName(data.name); setNodes(clientNodeItems); setConnections(canvasData.canvas.connections); setCanvasPosition(canvasData.canvas.position); }, [data]); const debouncedOnCodeChange = useMemo(() => debounce((code: string) => { //formik.setFieldValue("code", e, false); }, 700), []); const debouncedOnGraphUpdate = useMemo(() => debounce((graphData) => { const flatData = flattenGraphData(graphData); generateHttp(flatData) .then(checkHttpStatus) .then(data => { if (data['code'].length) { for (var i = 0; i < data['code'].length; ++i) { data['code'][i] = data['code'][i].replace(/(\r\n|\n|\r)/gm, ""); } const code = data['code'].join("\n"); setGeneratedCode(code); } }) .catch(err => { }) .finally(() => { }); }, 450), []); const onCodeUpdate = (code: string) => { debouncedOnCodeChange(code); }; const onGraphUpdate = (graphData: any) => { debouncedOnGraphUpdate(graphData); }; const onCanvasUpdate = (updatedCanvasPosition: any) => { setCanvasPosition({...canvasPosition, ...updatedCanvasPosition}); }; useEffect(() => { const handler = () => { setViewHeight(); } window.addEventListener("resize", handler); setViewHeight(); return () => { window.removeEventListener("resize", handler); } }, []); const onAddEndpoint = (values: any) => { let sections = flattenLibraries(nodeLibraries); let clientNodeItem = getClientNodeItem(values, ensure(sections.find((l) => l.Type === values.type))); clientNodeItem.position = { left: 60, top: 30 }; setNodes({ ...nodes, [clientNodeItem.key]: clientNodeItem }); } const onUpdateEndpoint = (nodeItem: IClientNodeItem) => { setNodes({ ...nodes, [nodeItem.key]: nodeItem }); } const onConnectionDetached = (data: any) => { if (!stateConnectionsRef.current || stateConnectionsRef.current.length <= 0) { return; } const _connections: [[string, string]] = [...stateConnectionsRef.current] as any; const existingIndex = getMatchingSetIndex(_connections, data); if (existingIndex !== -1) { _connections.splice(existingIndex, 1); } setConnections(_connections); } const onConnectionAttached = (data: any) => { if (stateConnectionsRef.current && stateConnectionsRef.current.length > 0) { const _connections: [[string, string]] = [...stateConnectionsRef.current] as any; const existingIndex = getMatchingSetIndex(_connections, data); if (existingIndex === -1) { _connections.push(data); } setConnections(_connections); } else { setConnections([data]); } } const onRemoveEndpoint = (node: IClientNodeItem) => { setNodes({ ...omit(nodes, node.key) }); eventBus.dispatch("NODE_DELETED", { message: { "node": node } }); } useEffect(() => { if (!generatedCode) { return; } if (language === "json") { setFormattedCode(JSON.stringify(YAML.parse(generatedCode), null, 2)); } if (language === "yaml") { setFormattedCode(generatedCode); } }, [language, generatedCode]); useEffect(() => { }, [nodeForEdit]); if (!isFetching) { return ( <> {showModalCreateService ? setShowModalCreateService(false)} onAddEndpoint={(values: any) => onAddEndpoint(values)} /> : null } {nodeForEdit ? setNodeForEdit(null)} onUpdateEndpoint={(values: any) => onUpdateEndpoint(values)} /> : null } {nodeForDelete ? setNodeForDelete(null)} onConfirm={() => { onRemoveEndpoint(nodeForDelete); setNodeForDelete(null); }} /> : null }
onNodeUpdate(node)} onGraphUpdate={(graphData: any) => onGraphUpdate(graphData)} onCanvasUpdate={(canvasData: any) => onCanvasUpdate(canvasData)} onConnectionAttached={(connectionData: any) => onConnectionAttached(connectionData)} onConnectionDetached={(connectionData: any) => onConnectionDetached(connectionData)} setNodeForEdit={(node: IClientNodeItem) => setNodeForEdit(node)} setNodeForDelete={(node: IClientNodeItem) => setNodeForDelete(node)} />
{ onCodeUpdate(e) }} disabled={false} lineWrapping={false} height={height - 64} />
) } return (
); }