mirror of https://github.com/ctk-hq/ctk
Merge pull request #63 from nuxxapp/feat/react-query
Replace Redux with React Queryremotes/origin/refactor/jwt-ls
commit
74b90ae4ff
@ -1,203 +1,400 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useRef, useMemo } from "react";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { IProjectPayload } from "../../types";
|
import { debounce, Dictionary, omit } from 'lodash';
|
||||||
import { nodes, connections, position, updateProjectName } from "../../reducers";
|
import YAML from "yaml";
|
||||||
import Spinner from "../Spinner";
|
import { PlusIcon } from "@heroicons/react/solid";
|
||||||
import { Canvas } from "../Canvas";
|
import { IProjectPayload, IClientNodeItem, IServiceNodePosition } from "../../types";
|
||||||
|
import eventBus from "../../events/eventBus";
|
||||||
|
import { useProject, useUpdateProject } from "../../hooks/useProject";
|
||||||
import useWindowDimensions from "../../hooks/useWindowDimensions";
|
import useWindowDimensions from "../../hooks/useWindowDimensions";
|
||||||
import { getClientNodesAndConnections } from "../../utils";
|
import { projectHttpCreate } from "../../services/project";
|
||||||
import { projectHttpGet, projectHttpUpdate, projectHttpCreate } from "../../services/project";
|
import { flattenGraphData } from "../../utils/generators";
|
||||||
import { checkHttpStatus } from "../../services/helpers";
|
|
||||||
import { nodeLibraries } from "../../utils/data/libraries";
|
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 {
|
interface IProjectProps {}
|
||||||
dispatch: any;
|
|
||||||
state: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Project(props: IProjectProps) {
|
export default function Project(props: IProjectProps) {
|
||||||
const { uuid } = useParams<{ uuid?: string }>();
|
|
||||||
const { dispatch, state } = props;
|
|
||||||
const [saving, setSaving] = useState(false);
|
|
||||||
const [projectName, setProjectName] = useState("");
|
|
||||||
const { height, width } = useWindowDimensions();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { uuid } = useParams<{ uuid: string }>();
|
||||||
|
const { height } = useWindowDimensions();
|
||||||
|
const { data, error, isFetching } = useProject(uuid);
|
||||||
|
const mutation = useUpdateProject(uuid);
|
||||||
|
const stateNodesRef = useRef<Dictionary<IClientNodeItem>>();
|
||||||
|
const stateConnectionsRef = useRef<[[string, string]] | []>();
|
||||||
|
|
||||||
const handleNameChange = (e: any) => {
|
const [generatedCode, setGeneratedCode] = useState<string>();
|
||||||
setProjectName(e.target.value);
|
const [formattedCode, setFormattedCode] = useState<string>("");
|
||||||
dispatch(updateProjectName(e.target.value));
|
const [showModalCreateService, setShowModalCreateService] = useState(false);
|
||||||
}
|
const [showVolumesModal, setShowVolumesModal] = useState(false);
|
||||||
|
const [showNetworksModal, setShowNetworksModal] = useState(false);
|
||||||
|
const [nodeForEdit, setNodeForEdit] = useState<IClientNodeItem | null>(null);
|
||||||
|
const [nodeForDelete, setNodeForDelete] = useState<IClientNodeItem | null>(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});
|
||||||
|
|
||||||
const updateProject = (uuid: string, payload: IProjectPayload) => {
|
stateNodesRef.current = nodes;
|
||||||
projectHttpUpdate(uuid, JSON.stringify(payload))
|
stateConnectionsRef.current = connections;
|
||||||
.then(checkHttpStatus)
|
|
||||||
.then(data => {
|
|
||||||
|
|
||||||
})
|
const handleNameChange = (e: any) => {
|
||||||
.catch(err => {
|
setProjectName(e.target.value);
|
||||||
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setSaving(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createProject = (payload: IProjectPayload) => {
|
const createProject = (payload: IProjectPayload) => {
|
||||||
projectHttpCreate(JSON.stringify(payload))
|
projectHttpCreate(JSON.stringify(payload))
|
||||||
.then(checkHttpStatus)
|
.then(checkHttpStatus)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
navigate(`/projects/${data.uuid}`)
|
navigate(`/projects/${data.uuid}`);
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
.catch(err => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setSaving(false);
|
})
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const onNodeUpdate = (positionData: IServiceNodePosition) => {
|
||||||
|
if (stateNodesRef.current) {
|
||||||
|
const node = { ...stateNodesRef.current[positionData.key], ...positionData };
|
||||||
|
setNodes({ ...stateNodesRef.current, [positionData.key]: node });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSave = () => {
|
const onSave = () => {
|
||||||
setSaving(true);
|
|
||||||
const payload: IProjectPayload = {
|
const payload: IProjectPayload = {
|
||||||
name: state.projectName,
|
name: projectName,
|
||||||
data: {
|
data: {
|
||||||
canvas: {
|
canvas: {
|
||||||
position: state.canvasPosition,
|
position: canvasPosition,
|
||||||
nodes: state.nodes,
|
nodes: nodes,
|
||||||
connections: state.connections
|
connections: connections
|
||||||
},
|
},
|
||||||
configs: [],
|
configs: [],
|
||||||
networks: [],
|
networks: [],
|
||||||
secrets: [],
|
secrets: [],
|
||||||
services: state.nodes,
|
services: nodes,
|
||||||
version: 3,
|
version: 3,
|
||||||
volumes: [],
|
volumes: []
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
if (uuid) {
|
if (uuid) {
|
||||||
updateProject(uuid, payload);
|
mutation.mutate(payload);
|
||||||
} else {
|
} else {
|
||||||
createProject(payload);
|
createProject(payload);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const setViewHeight = () => {
|
const setViewHeight = () => {
|
||||||
let vh = window.innerHeight * 0.01;
|
let vh = window.innerHeight * 0.01;
|
||||||
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
document.documentElement.style.setProperty("--vh", `${vh}px`);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
const copy = () => {
|
||||||
|
navigator.clipboard.writeText(formattedCode);
|
||||||
|
setCopyText("Copied");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopyText("Copy");
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (uuid) {
|
if (!data) {
|
||||||
projectHttpGet(uuid)
|
return;
|
||||||
.then(checkHttpStatus)
|
|
||||||
.then(data => {
|
|
||||||
const projectData = JSON.parse(data.data);
|
|
||||||
const nodesAsList = Object.keys(projectData.canvas.nodes).map((k) => {
|
|
||||||
return projectData.canvas.nodes[k];
|
|
||||||
});
|
|
||||||
|
|
||||||
const clientNodeItems = getClientNodesAndConnections(nodesAsList, nodeLibraries);
|
|
||||||
setProjectName(data.name);
|
|
||||||
dispatch(updateProjectName(data.name));
|
|
||||||
dispatch(nodes(clientNodeItems));
|
|
||||||
dispatch(connections(projectData.canvas.connections));
|
|
||||||
dispatch(position(projectData.canvas.position));
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
if (err.status === 404) {
|
|
||||||
window.location.replace("/");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
//setFetching(false);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}, [uuid, dispatch]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
console.log(data);
|
||||||
//setProjectName(state.projectName);
|
|
||||||
}, [state.projectName]);
|
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(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("resize", () => {
|
const handler = () => {
|
||||||
setViewHeight();
|
setViewHeight();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", handler);
|
||||||
setViewHeight();
|
setViewHeight();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", handler);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
const onAddEndpoint = (values: any) => {
|
||||||
<>
|
let sections = flattenLibraries(nodeLibraries);
|
||||||
<div className="px-4 py-3 border-b border-gray-200">
|
let clientNodeItem = getClientNodeItem(values, ensure(sections.find((l) => l.Type === values.type)));
|
||||||
<form className="flex flex-col space-y-2 md:flex-row md:justify-between items-center" autoComplete="off">
|
clientNodeItem.position = { left: 60, top: 30 };
|
||||||
<input
|
setNodes({ ...nodes, [clientNodeItem.key]: clientNodeItem });
|
||||||
className={`
|
}
|
||||||
bg-gray-100
|
|
||||||
appearance-none
|
const onUpdateEndpoint = (nodeItem: IClientNodeItem) => {
|
||||||
w-full
|
setNodes({ ...nodes, [nodeItem.key]: nodeItem });
|
||||||
md:w-1/2
|
}
|
||||||
lg:w-1/3
|
|
||||||
block
|
const onConnectionDetached = (data: any) => {
|
||||||
text-gray-700
|
if (!stateConnectionsRef.current || stateConnectionsRef.current.length <= 0) {
|
||||||
border
|
return;
|
||||||
border-gray-100
|
}
|
||||||
dark:bg-gray-900
|
|
||||||
dark:text-white
|
const _connections: [[string, string]] = [...stateConnectionsRef.current] as any;
|
||||||
dark:border-gray-900
|
const existingIndex = getMatchingSetIndex(_connections, data);
|
||||||
rounded
|
|
||||||
py-2
|
if (existingIndex !== -1) {
|
||||||
px-3
|
_connections.splice(existingIndex, 1);
|
||||||
leading-tight
|
}
|
||||||
focus:outline-none
|
|
||||||
focus:border-indigo-400
|
setConnections(_connections);
|
||||||
focus:ring-0
|
}
|
||||||
`}
|
|
||||||
type="text"
|
const onConnectionAttached = (data: any) => {
|
||||||
placeholder="Untitled"
|
if (stateConnectionsRef.current && stateConnectionsRef.current.length > 0) {
|
||||||
autoComplete="off"
|
const _connections: [[string, string]] = [...stateConnectionsRef.current] as any;
|
||||||
id="name"
|
const existingIndex = getMatchingSetIndex(_connections, data);
|
||||||
name="name"
|
if (existingIndex === -1) {
|
||||||
onChange={handleNameChange}
|
_connections.push(data);
|
||||||
value={projectName}
|
}
|
||||||
|
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
|
||||||
|
? <ModalServiceCreate
|
||||||
|
onHide={() => setShowModalCreateService(false)}
|
||||||
|
onAddEndpoint={(values: any) => onAddEndpoint(values)}
|
||||||
/>
|
/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
<div className="flex flex-col space-y-2 w-full justify-end mb-4 md:flex-row md:space-y-0 md:space-x-2 md:mb-0">
|
{nodeForEdit
|
||||||
<button
|
? <ModalServiceEdit
|
||||||
onClick={() => {
|
node={nodeForEdit}
|
||||||
window.location.replace("/");
|
onHide={() => setNodeForEdit(null)}
|
||||||
}}
|
onUpdateEndpoint={(values: any) => onUpdateEndpoint(values)}
|
||||||
type="button"
|
/>
|
||||||
className="btn-util text-black bg-gray-200 hover:bg-gray-300 sm:w-auto"
|
: null
|
||||||
>
|
}
|
||||||
<div className="flex justify-center items-center space-x-2 mx-auto">
|
|
||||||
<span>New</span>
|
{nodeForDelete
|
||||||
</div>
|
? <ModalConfirmDelete
|
||||||
</button>
|
onHide={() => setNodeForDelete(null)}
|
||||||
|
onConfirm={() => {
|
||||||
<button
|
onRemoveEndpoint(nodeForDelete);
|
||||||
onClick={() => onSave()}
|
setNodeForDelete(null);
|
||||||
type="button"
|
}}
|
||||||
className="btn-util text-white bg-green-600 hover:bg-green-700 sm:w-auto"
|
/>
|
||||||
>
|
: null
|
||||||
<div className="flex justify-center items-center space-x-2 mx-auto">
|
}
|
||||||
{saving &&
|
|
||||||
<Spinner className="w-4 h-4 text-green-300" />
|
<div className="px-4 py-3 border-b border-gray-200">
|
||||||
}
|
<form
|
||||||
<span>Save</span>
|
className="flex flex-col space-y-2 md:flex-row md:justify-between items-center"
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className={`
|
||||||
|
bg-gray-100
|
||||||
|
appearance-none
|
||||||
|
w-full
|
||||||
|
md:w-1/2
|
||||||
|
lg:w-1/3
|
||||||
|
block
|
||||||
|
text-gray-700
|
||||||
|
border
|
||||||
|
border-gray-100
|
||||||
|
dark:bg-gray-900
|
||||||
|
dark:text-white
|
||||||
|
dark:border-gray-900
|
||||||
|
rounded
|
||||||
|
py-2
|
||||||
|
px-3
|
||||||
|
leading-tight
|
||||||
|
focus:outline-none
|
||||||
|
focus:border-indigo-400
|
||||||
|
focus:ring-0
|
||||||
|
`}
|
||||||
|
type="text"
|
||||||
|
placeholder="Untitled"
|
||||||
|
autoComplete="off"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
onChange={handleNameChange}
|
||||||
|
value={projectName}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-col space-y-2 w-full justify-end mb-4 md:flex-row md:space-y-0 md:space-x-2 md:mb-0">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
window.location.replace("/projects/new")
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
className="btn-util text-black bg-gray-200 hover:bg-gray-300 sm:w-auto"
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-center space-x-2 mx-auto">
|
||||||
|
<span>New</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => onSave()}
|
||||||
|
type="button"
|
||||||
|
className="btn-util text-white bg-green-600 hover:bg-green-700 sm:w-auto"
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-center space-x-2 mx-auto">
|
||||||
|
{mutation.isLoading && <Spinner className="w-4 h-4 text-green-300" />}
|
||||||
|
<span>Save</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-grow relative flex-col md:flex-row">
|
||||||
|
<div className="w-full overflow-hidden md:w-2/3 z-40" style={{ height: height }}>
|
||||||
|
<div className="relative h-full">
|
||||||
|
<div className="absolute top-0 right-0 z-40">
|
||||||
|
<div className="flex space-x-2 p-2">
|
||||||
|
<button className="flex space-x-1 btn-util" type="button" onClick={() => setShowModalCreateService(true)}>
|
||||||
|
<PlusIcon className="w-3" />
|
||||||
|
<span>Service</span>
|
||||||
|
</button>
|
||||||
|
<button className="btn-util" type="button" onClick={() => setShowVolumesModal(true)}>
|
||||||
|
Volumes
|
||||||
|
</button>
|
||||||
|
<button className="btn-util" type="button" onClick={() => setShowNetworksModal(true)}>
|
||||||
|
Networks
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
|
||||||
|
<Canvas
|
||||||
|
nodes={nodes}
|
||||||
|
connections={connections}
|
||||||
|
canvasPosition={canvasPosition}
|
||||||
|
onNodeUpdate={(node: IServiceNodePosition) => 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)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative group code-column w-full md:w-1/3">
|
||||||
|
<div className={`absolute top-0 left-0 right-0 z-10 flex justify-end p-1 space-x-2 group-hover:visible invisible`}>
|
||||||
|
<button className={`btn-util ${language === "json" ? `btn-util-selected` : ``}`} onClick={() => setLanguage('json')}>json</button>
|
||||||
|
<button className={`btn-util ${language === "yaml" ? `btn-util-selected` : ``}`} onClick={() => setLanguage('yaml')}>yaml</button>
|
||||||
|
<button className="btn-util" type="button" onClick={copy}>{copyText}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CodeEditor
|
||||||
|
data={formattedCode}
|
||||||
|
language={language}
|
||||||
|
onChange={(e: any) => { onCodeUpdate(e) }}
|
||||||
|
disabled={false}
|
||||||
|
lineWrapping={false}
|
||||||
|
height={height - 64}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</>
|
||||||
|
)
|
||||||
<div className="flex flex-grow relative flex-col md:flex-row">
|
}
|
||||||
<Canvas
|
|
||||||
state={state}
|
return (
|
||||||
dispatch={dispatch}
|
<div className="flex items-center justify-center items-stretch min-h-screen align-middle">
|
||||||
height={(height - 64)}
|
<Spinner className="w-4 h-4 m-auto dark:text-blue-400 text-blue-600"></Spinner>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
import React, { SyntheticEvent } from "react";
|
|
||||||
import { TrashIcon } from "@heroicons/react/solid";
|
|
||||||
|
|
||||||
|
|
||||||
export interface ICloseProps {
|
|
||||||
id: string;
|
|
||||||
onClose?: (id: string, source?: string, target?: string) => any;
|
|
||||||
source?: string;
|
|
||||||
target?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Close = (props: ICloseProps) => {
|
|
||||||
const { id, onClose, source, target } = props;
|
|
||||||
const handleClose = (event: SyntheticEvent<HTMLDivElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (onClose) {
|
|
||||||
onClose(id, source, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='absolute -top-4 left-0' onClick={handleClose} title={id || 'UNKNOWN'}>
|
|
||||||
<TrashIcon className="w-3.5 text-gray-500" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Close
|
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import axios from "axios"
|
||||||
|
import { useQuery, useMutation, useQueryClient, QueryClient } from "react-query";
|
||||||
|
import { API_SERVER_URL } from "../constants";
|
||||||
|
import { IProjectPayload } from "../types";
|
||||||
|
|
||||||
|
const fetchProjectByUuid = async (uuid: string) => {
|
||||||
|
const response = await axios.get(`${API_SERVER_URL}/projects/${uuid}/`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateProjectByUuid = async (uuid: string, data: string) => {
|
||||||
|
const response = await axios({
|
||||||
|
method: 'put',
|
||||||
|
url: `${API_SERVER_URL}/projects/${uuid}/`,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProject = (uuid: string | undefined) => {
|
||||||
|
return useQuery(
|
||||||
|
["projects", uuid],
|
||||||
|
async () => {
|
||||||
|
if (!uuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return await fetchProjectByUuid(uuid);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
staleTime: Infinity
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateProject = (uuid: string | undefined) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
async (projectData: IProjectPayload) => {
|
||||||
|
if (!uuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await updateProjectByUuid(uuid, JSON.stringify(projectData));
|
||||||
|
return data;
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.response.status === 404) {
|
||||||
|
console.log('Resource could not be found!');
|
||||||
|
} else {
|
||||||
|
console.log(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: (projectData) => {
|
||||||
|
queryClient.setQueryData(['projects', uuid], projectData);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue