task: add eslint and prettier

pull/68/head
Artem Golub 3 years ago committed by Samuel Rowe
parent 07ae98b432
commit 386b5e137b

@ -0,0 +1,20 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"prettier"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"no-console": 1, // Means warning
"prettier/prettier": 2, // Means error
"no-empty-function": 1,
"@typescript-eslint/no-empty-function": 1
}
}

@ -0,0 +1,5 @@
{
"semi": true,
"trailingComma": "none",
"printWidth": 80
}

@ -25,7 +25,7 @@
"@jsplumb/connector-bezier": "^5.5.2",
"@jsplumb/core": "^5.5.2",
"@jsplumb/util": "^5.5.2",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.1",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.2",
@ -51,7 +51,9 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
"lint": "eslint . --ext .ts"
},
"eslintConfig": {
"extends": [
@ -79,7 +81,11 @@
"@types/react": "^17.0.45",
"@types/react-dom": "^17.0.11",
"@types/uuid": "^8.3.4",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.6"
"autoprefixer": "10.4.7",
"eslint": "^8.19.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"postcss": "^8.4.14",
"prettier": "^2.7.1"
}
}

@ -12,7 +12,7 @@ import { authSelf } from "./reducers";
import { refresh, self } from "./services/auth";
import SideBar from "./components/global/SideBar";
import Projects from "./components/Projects"
import Projects from "./components/Projects";
import Project from "./components/Project";
import Profile from "./components/Profile";
import Signup from "./components/Auth/Signup";
@ -33,16 +33,16 @@ export default function App() {
const defaultProtectedRouteProps: Omit<ProtectedRouteProps, "outlet"> = {
isAuthenticated: isAuthenticated,
authenticationPath: "/login"
}
};
useEffect(() => {
if (isAuthenticated) {
self()
.then(checkHttpStatus)
.then(data => {
.then((data) => {
dispatch(authSelf(data));
})
.catch(err => {
.catch((err) => {
// since auth is set in localstorage,
// try to refresh the existing token,
// on error clear localstorage
@ -56,7 +56,7 @@ export default function App() {
refresh()
.then(checkHttpStatus)
.then(data => {
.then((data) => {
const localData = localStorage.getItem(LOCAL_STORAGE);
if (localData) {
@ -70,11 +70,11 @@ export default function App() {
}
}
})
.catch(err => {
.catch(() => {
localStorage.removeItem(LOCAL_STORAGE);
})
});
}
})
});
}
}, [dispatch, isAuthenticated]);
@ -84,15 +84,9 @@ export default function App() {
<Toaster />
<SideBar isAuthenticated={isAuthenticated} state={state} />
<Routes>
<Route
path="/projects/:uuid"
element={<Project />}
/>
<Route path="/projects/:uuid" element={<Project />} />
<Route
path="/projects/new"
element={<Project />}
/>
<Route path="/projects/new" element={<Project />} />
<Route
path="/"
@ -123,6 +117,7 @@ export default function App() {
/>
}
/>
<Route path="/signup" element={<Signup dispatch={dispatch} />} />
<Route path="/login" element={<Login dispatch={dispatch} />} />
</Routes>
@ -130,5 +125,5 @@ export default function App() {
<ReactQueryDevtools initialIsOpen={true} />
</QueryClientProvider>
)
);
}

@ -22,37 +22,41 @@ const Login = (props: IProfileProps) => {
username: "",
password: ""
},
onSubmit: values => {
onSubmit: (values) => {
const username = values.username;
const password = values.password;
setLoggingIn(true);
if (username && password) {
logIn(username, password)
.then(checkHttpStatus)
.then(data => {
localStorage.setItem(LOCAL_STORAGE, JSON.stringify({
"access_token": data.access_token,
"refresh_token": data.refresh_token
}))
dispatch(authLoginSuccess(data))
.then((data) => {
localStorage.setItem(
LOCAL_STORAGE,
JSON.stringify({
access_token: data.access_token,
refresh_token: data.refresh_token
})
);
dispatch(authLoginSuccess(data));
navigate("/");
})
.catch(err => {
.catch((err) => {
if (err) {
err.text().then((e: string) => {
toaster(e, "error");
})
});
}
}).finally(() => {
setLoggingIn(false);
})
.finally(() => {
setLoggingIn(false);
});
}
},
}
});
return (
<>
<div className="flex flex-col">
<main className="py-6 md:w-2/3 lg:w-1/4 mx-auto">
<h2 className="mb-4 px-4 sm:px-6 md:flex-row md:px-8 text-xl font-extrabold dark:text-white text-gray-900">
@ -130,9 +134,7 @@ const Login = (props: IProfileProps) => {
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-2.5 py-1.5 bg-green-600 text-sm font-medium text-white hover:bg-green-700 sm:w-auto text-sm"
>
<div className="flex justify-center items-center space-x-2">
{loggingIn &&
<Spinner className="w-5 h-5 text-green-300" />
}
{loggingIn && <Spinner className="w-5 h-5 text-green-300" />}
<span>Login</span>
</div>
</button>
@ -150,7 +152,7 @@ const Login = (props: IProfileProps) => {
</main>
</div>
</>
)
}
);
};
export default Login;

@ -24,35 +24,39 @@ const Signup = (props: IProfileProps) => {
password: "",
confirmPassword: ""
},
onSubmit: values => {
onSubmit: (values) => {
const username = values.username;
const email = values.email;
const password1 = values.password;
const password2 = values.confirmPassword;
setSigningUp(true);
if (username && email && password1 && password2) {
signup(username, email, password1, password2)
.then(checkHttpStatus)
.then(data => {
localStorage.setItem(LOCAL_STORAGE, JSON.stringify({
"access_token": data.access_token,
"refresh_token": data.refresh_token
}))
dispatch(authLoginSuccess(data))
.then((data) => {
localStorage.setItem(
LOCAL_STORAGE,
JSON.stringify({
access_token: data.access_token,
refresh_token: data.refresh_token
})
);
dispatch(authLoginSuccess(data));
navigate("/");
})
.catch(err => {
.catch((err) => {
if (err) {
err.text().then((e: string) => {
toaster(e, "error");
})
});
}
}).finally(() => {
setSigningUp(false);
})
.finally(() => {
setSigningUp(false);
});
}
},
}
});
return (
@ -198,9 +202,7 @@ const Signup = (props: IProfileProps) => {
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-2.5 py-1.5 bg-green-600 text-sm font-medium text-white hover:bg-green-700 sm:w-auto text-sm"
>
<div className="flex justify-center items-center space-x-2">
{signingUp &&
<Spinner className="w-5 h-5 text-green-300" />
}
{signingUp && <Spinner className="w-5 h-5 text-green-300" />}
<span>Signup</span>
</div>
</button>
@ -218,7 +220,7 @@ const Signup = (props: IProfileProps) => {
</main>
</div>
</>
)
}
);
};
export default Signup;

@ -1,22 +1,30 @@
import { TrashIcon, PencilIcon } from "@heroicons/react/solid";
import { CallbackFunction } from "../../types";
export const Popover = ({
onEditClick,
onDeleteClick
}: {
onEditClick: Function
onDeleteClick: Function
onEditClick: CallbackFunction;
onDeleteClick: CallbackFunction;
}) => {
return (
<div className="relative flex flex-col items-center">
<div className="flex absolute -bottom-2 flex flex-col items-center p-2">
<span className="relative z-10 p-2.5 text-xs leading-none text-white whitespace-no-wrap bg-gray-700 shadow-lg rounded-md">
<div className="flex space-x-2.5">
<TrashIcon onClick={() => onDeleteClick()} className="w-3 h-3 text-red-400"></TrashIcon>
<PencilIcon onClick={() => onEditClick()} className="w-3 h-3"></PencilIcon>
<TrashIcon
onClick={() => onDeleteClick()}
className="w-3 h-3 text-red-400"
></TrashIcon>
<PencilIcon
onClick={() => onEditClick()}
className="w-3 h-3"
></PencilIcon>
</div>
</span>
<div className="w-3 h-3 -mt-2.5 rotate-45 bg-gray-600"></div>
</div>
</div>
);
};
};

@ -3,7 +3,7 @@ import { values } from "lodash";
import { v4 as uuidv4 } from "uuid";
import eventBus from "../../events/eventBus";
import { Popover } from "./Popover";
import { IGraphData } from "../../types";
import { IGraphData, CallbackFunction } from "../../types";
import { useJsPlumb } from "../useJsPlumb";
const CANVAS_ID: string = "canvas-container-" + uuidv4();
@ -12,13 +12,13 @@ interface ICanvasProps {
nodes: any;
connections: any;
canvasPosition: any;
onNodeUpdate: any;
onGraphUpdate: any;
onCanvasUpdate: any;
onConnectionAttached: any;
onConnectionDetached: any;
setNodeForEdit: any;
setNodeForDelete: any;
onNodeUpdate: CallbackFunction;
onGraphUpdate: CallbackFunction;
onCanvasUpdate: CallbackFunction;
onConnectionAttached: CallbackFunction;
onConnectionDetached: CallbackFunction;
setNodeForEdit: CallbackFunction;
setNodeForDelete: CallbackFunction;
}
export const Canvas: FC<ICanvasProps> = (props) => {
@ -44,16 +44,18 @@ export const Canvas: FC<ICanvasProps> = (props) => {
const [_initX, _setInitX] = useState(0);
const [_initY, _setInitY] = useState(0);
let translateWidth = (document.documentElement.clientWidth * (1 - _scale)) / 2;
let translateHeight = ((document.documentElement.clientHeight - 64) * (1 - _scale)) / 2;
const translateWidth =
(document.documentElement.clientWidth * (1 - _scale)) / 2;
const translateHeight =
((document.documentElement.clientHeight - 64) * (1 - _scale)) / 2;
const [containerCallbackRef, setZoom, setStyle, removeEndpoint] = useJsPlumb(
nodes,
connections,
((graphData: IGraphData) => onGraphUpdate(graphData)),
((positionData: any) => onNodeUpdate(positionData)),
((connectionData: any) => onConnectionAttached(connectionData)),
((connectionData: any) => onConnectionDetached(connectionData))
(graphData: IGraphData) => onGraphUpdate(graphData),
(positionData: any) => onNodeUpdate(positionData),
(connectionData: any) => onConnectionAttached(connectionData),
(connectionData: any) => onConnectionDetached(connectionData)
);
const onCanvasMousewheel = (e: any) => {
@ -66,12 +68,12 @@ export const Canvas: FC<ICanvasProps> = (props) => {
_setScale(_scale - _scale * 0.25);
setScale(_scale - _scale * 0.25);
}
}
};
const onCanvasMouseUpLeave = (e: any) => {
if (dragging) {
let left = _left + e.pageX - _initX;
let top = _top + e.pageY - _initY;
const left = _left + e.pageX - _initX;
const top = _top + e.pageY - _initY;
_setLeft(left);
_setTop(top);
@ -81,7 +83,7 @@ export const Canvas: FC<ICanvasProps> = (props) => {
top: top
});
}
}
};
const onCanvasMouseMove = (e: any) => {
if (!dragging) {
@ -89,18 +91,18 @@ export const Canvas: FC<ICanvasProps> = (props) => {
}
const styles = {
"left": _left + e.pageX - _initX + 'px',
"top": _top + e.pageY - _initY + 'px'
}
left: _left + e.pageX - _initX + "px",
top: _top + e.pageY - _initY + "px"
};
setStyle(styles);
}
};
const onCanvasMouseDown = (e: any) => {
_setInitX(e.pageX);
_setInitY(e.pageY);
setDragging(true);
}
};
useEffect(() => {
setZoom(_scale);
@ -114,9 +116,9 @@ export const Canvas: FC<ICanvasProps> = (props) => {
useEffect(() => {
const styles = {
"left": _left + 'px',
"top": _top + 'px'
}
left: _left + "px",
top: _top + "px"
};
setStyle(styles);
}, [_left, _top, setStyle]);
@ -132,7 +134,7 @@ export const Canvas: FC<ICanvasProps> = (props) => {
setNodeDragging(data.detail.message.id);
});
eventBus.on("EVENT_DRAG_STOP", (data: any) => {
eventBus.on("EVENT_DRAG_STOP", () => {
setNodeDragging(null);
});
@ -141,36 +143,43 @@ export const Canvas: FC<ICanvasProps> = (props) => {
});
return () => {
eventBus.remove("NODE_DELETED", () => { });
eventBus.remove("EVENT_DRAG_START", () => {});
eventBus.remove("EVENT_DRAG_STOP", () => { });
eventBus.remove("NODE_DELETED", () => undefined);
eventBus.remove("EVENT_DRAG_START", () => undefined);
eventBus.remove("EVENT_DRAG_STOP", () => undefined);
};
}, []);
return (
<>
{nodes &&
<div key={CANVAS_ID} className="jsplumb-box"
{nodes && (
<div
key={CANVAS_ID}
className="jsplumb-box"
onWheel={onCanvasMousewheel}
onMouseMove={onCanvasMouseMove}
onMouseDown={onCanvasMouseDown}
onMouseUp={onCanvasMouseUpLeave}
onMouseLeave={onCanvasMouseUpLeave}
onContextMenu={(event) => { event.stopPropagation(); event.preventDefault(); }}
onContextMenu={(event) => {
event.stopPropagation();
event.preventDefault();
}}
>
<div
id={CANVAS_ID}
ref={containerCallbackRef}
className="canvas h-full w-full"
style={{
transformOrigin: '0px 0px 0px',
transformOrigin: "0px 0px 0px",
transform: `translate(${translateWidth}px, ${translateHeight}px) scale(${_scale})`
}}
>
{values(nodes).map((x) => (
<div
key={x.key}
className={"node-item cursor-pointer shadow flex flex-col group"}
className={
"node-item cursor-pointer shadow flex flex-col group"
}
id={x.key}
style={{ top: x.position.top, left: x.position.left }}
onMouseEnter={() => setNodeHovering(x.key)}
@ -180,7 +189,7 @@ export const Canvas: FC<ICanvasProps> = (props) => {
}
}}
>
{((nodeHovering === x.key) && (nodeDragging !== x.key)) &&
{nodeHovering === x.key && nodeDragging !== x.key && (
<Popover
onEditClick={() => {
setNodeForEdit(x);
@ -189,7 +198,7 @@ export const Canvas: FC<ICanvasProps> = (props) => {
setNodeForDelete(x);
}}
></Popover>
}
)}
<div className="node-label w-full py-2 px-4">
<div className="text-sm font-semibold">
{x.configuration.prettyName}
@ -202,7 +211,7 @@ export const Canvas: FC<ICanvasProps> = (props) => {
))}
</div>
</div>
}
)}
</>
);
};

@ -5,118 +5,118 @@ import {
highlightSpecialChars,
drawSelection,
highlightActiveLine,
keymap,
} from '@codemirror/view'
keymap
} from "@codemirror/view";
import { jsonLanguage } from "@codemirror/lang-json";
import { yaml } from "@codemirror/legacy-modes/mode/yaml";
import { history, historyKeymap } from '@codemirror/history'
import { foldGutter, foldKeymap } from '@codemirror/fold'
import { bracketMatching } from '@codemirror/matchbrackets'
import { closeBrackets, closeBracketsKeymap } from '@codemirror/closebrackets'
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'
import { autocompletion, completionKeymap } from '@codemirror/autocomplete'
import { rectangularSelection } from '@codemirror/rectangular-selection'
import { commentKeymap } from '@codemirror/comment'
import { lintKeymap } from '@codemirror/lint'
import { indentOnInput, LanguageSupport } from '@codemirror/language'
import { lineNumbers } from '@codemirror/gutter';
import { defaultKeymap, indentMore, indentLess } from '@codemirror/commands'
import { defaultHighlightStyle } from '@codemirror/highlight'
import { solarizedDark } from './themes/ui/dark'
import darkHighlightStyle from './themes/highlight/dark'
import { history, historyKeymap } from "@codemirror/history";
import { foldGutter, foldKeymap } from "@codemirror/fold";
import { bracketMatching } from "@codemirror/matchbrackets";
import { closeBrackets, closeBracketsKeymap } from "@codemirror/closebrackets";
import { searchKeymap, highlightSelectionMatches } from "@codemirror/search";
import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
import { rectangularSelection } from "@codemirror/rectangular-selection";
import { commentKeymap } from "@codemirror/comment";
import { lintKeymap } from "@codemirror/lint";
import { indentOnInput, LanguageSupport } from "@codemirror/language";
import { lineNumbers } from "@codemirror/gutter";
import { defaultKeymap, indentMore, indentLess } from "@codemirror/commands";
import { defaultHighlightStyle } from "@codemirror/highlight";
import { solarizedDark } from "./themes/ui/dark";
import darkHighlightStyle from "./themes/highlight/dark";
import useCodeMirror from "./useCodeMirror";
interface ICodeEditorProps {
data: string;
language: string;
onChange: any;
disabled: boolean;
lineWrapping: boolean;
height: number
height: number;
}
const languageExtensions: any = {
json: [new LanguageSupport(jsonLanguage)],
yaml: [StreamLanguage.define(yaml)],
blank: undefined
}
};
const themeExtensions = {
light: [defaultHighlightStyle],
dark: [solarizedDark]
}
};
const highlightExtensions = {
dark: darkHighlightStyle
}
};
const CodeEditor = (props: ICodeEditorProps) => {
const { data, language, onChange, disabled, lineWrapping, height } = props;
const extensions = [[
lineNumbers(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
indentOnInput(),
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
highlightActiveLine(),
highlightSelectionMatches(),
...(languageExtensions[language]
? languageExtensions[language]
: []),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...commentKeymap,
...completionKeymap,
...lintKeymap,
{
key: "Tab",
preventDefault: true,
run: indentMore,
},
{
key: "Shift-Tab",
preventDefault: true,
run: indentLess,
},
/*{
const extensions = [
[
lineNumbers(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
indentOnInput(),
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
highlightActiveLine(),
highlightSelectionMatches(),
...(languageExtensions[language] ? languageExtensions[language] : []),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...commentKeymap,
...completionKeymap,
...lintKeymap,
{
key: "Tab",
preventDefault: true,
run: indentMore
},
{
key: "Shift-Tab",
preventDefault: true,
run: indentLess
}
/*{
key: "Ctrl-S",
preventDefault: true,
run: indentLess,
}*/
]),
EditorView.updateListener.of((update) => {
if (update.changes) {
onChange(update.state.doc.toString());
}
}),
EditorState.allowMultipleSelections.of(true),
...(disabled
? [EditorState.readOnly.of(true)]
: [EditorState.readOnly.of(false)]),
...(lineWrapping
? [EditorView.lineWrapping]
: []),
...[themeExtensions["dark"]],
...[highlightExtensions["dark"]]
]];
]),
EditorView.updateListener.of((update) => {
if (update.changes) {
onChange(update.state.doc.toString());
}
}),
EditorState.allowMultipleSelections.of(true),
...(disabled
? [EditorState.readOnly.of(true)]
: [EditorState.readOnly.of(false)]),
...(lineWrapping ? [EditorView.lineWrapping] : []),
...[themeExtensions["dark"]],
...[highlightExtensions["dark"]]
]
];
const { ref } = useCodeMirror(extensions, data);
return (
<div className={`overflow-y-auto py-2`} style={{ height: height }} ref={ref}>
</div>
)
}
<div
className={`overflow-y-auto py-2`}
style={{ height: height }}
ref={ref}
></div>
);
};
export default CodeEditor;

@ -2,7 +2,7 @@
// Copyright (C) 2018-2021 by Marijn Haverbeke <marijnh@gmail.com> and others
// MIT License: https://github.com/codemirror/theme-one-dark/blob/main/LICENSE
import { HighlightStyle, tags as t } from "@codemirror/highlight"
import { HighlightStyle, tags as t } from "@codemirror/highlight";
const chalky = "#e5c07b",
coral = "#e06c75",
@ -13,7 +13,7 @@ const chalky = "#e5c07b",
malibu = "#61afef",
sage = "#98c379",
whiskey = "#d19a66",
violet = "#c678dd"
violet = "#c678dd";
/// The highlighting style for code in the One Dark theme.
export default HighlightStyle.define([
@ -42,11 +42,28 @@ export default HighlightStyle.define([
color: ivory
},
{
tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
tag: [
t.typeName,
t.className,
t.number,
t.changed,
t.annotation,
t.modifier,
t.self,
t.namespace
],
color: chalky
},
{
tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)],
tag: [
t.operator,
t.operatorKeyword,
t.url,
t.escape,
t.regexp,
t.link,
t.special(t.string)
],
color: cyan
},
{
@ -78,5 +95,5 @@ export default HighlightStyle.define([
{
tag: t.invalid,
color: invalid
},
]);
}
]);

@ -1,93 +1,139 @@
import { EditorView } from '@codemirror/view';
import { HighlightStyle, tags } from '@codemirror/highlight';
import { EditorView } from "@codemirror/view";
import { HighlightStyle, tags } from "@codemirror/highlight";
const base00 = '#1F2937', base01 = '#073642', base02 = '#586e75', base03 = '#657b83', base04 = '#839496', base05 = '#dfdfdf', base06 = '#efefef', base07 = '#fdf6e3', base_red = '#dc322f', base_orange = '#cb4b16', base_yellow = '#e9b100', base_green = '#cfec11', base_cyan = '#44e0d4', base_blue = '#75c6ff', base_violet = '#a1a6ff', base_magenta = '#d33682';
const invalid = '#d30102', stone = base04, darkBackground = '#1F2937', highlightBackground = '#173541', background = base00, tooltipBackground = base01, selection = '#173541', cursor = base04;
const base00 = "#1F2937",
base01 = "#073642",
base02 = "#586e75",
base03 = "#657b83",
base04 = "#839496",
base05 = "#dfdfdf",
base06 = "#efefef",
base07 = "#fdf6e3",
base_red = "#dc322f",
base_orange = "#cb4b16",
base_yellow = "#e9b100",
base_green = "#cfec11",
base_cyan = "#44e0d4",
base_blue = "#75c6ff",
base_violet = "#a1a6ff",
base_magenta = "#d33682";
const invalid = "#d30102",
stone = base04,
darkBackground = "#1F2937",
highlightBackground = "#173541",
background = base00,
tooltipBackground = base01,
selection = "#173541",
cursor = base04;
/**
The editor theme styles for Solarized Dark.
*/
const solarizedDarkTheme = /*@__PURE__*/EditorView.theme({
'&': {
fontSize: "10.5pt",
color: base05,
backgroundColor: background
},
'.cm-content': {
caretColor: cursor
},
'.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': { backgroundColor: selection },
'.cm-panels': { backgroundColor: darkBackground, color: base03 },
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
'.cm-searchMatch': {
backgroundColor: '#72a1ff59',
outline: '1px solid #457dff'
},
'.cm-searchMatch.cm-searchMatch-selected': {
backgroundColor: '#6199ff2f'
},
'.cm-activeLine': { backgroundColor: highlightBackground },
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
outline: `1px solid ${base06}`
},
'.cm-gutters': {
backgroundColor: darkBackground,
color: stone,
border: 'none'
},
'.cm-activeLineGutter': {
backgroundColor: highlightBackground
},
'.cm-foldPlaceholder': {
backgroundColor: 'transparent',
border: 'none',
color: '#ddd'
},
'.cm-tooltip': {
border: 'none',
backgroundColor: tooltipBackground
},
'.cm-tooltip .cm-tooltip-arrow:before': {
borderTopColor: 'transparent',
borderBottomColor: 'transparent'
},
'.cm-tooltip .cm-tooltip-arrow:after': {
borderTopColor: tooltipBackground,
borderBottomColor: tooltipBackground
},
'.cm-tooltip-autocomplete': {
'& > ul > li[aria-selected]': {
backgroundColor: highlightBackground,
color: base03
const solarizedDarkTheme = /*@__PURE__*/ EditorView.theme(
{
"&": {
fontSize: "10.5pt",
color: base05,
backgroundColor: background
},
".cm-content": {
caretColor: cursor
},
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
{ backgroundColor: selection },
".cm-panels": { backgroundColor: darkBackground, color: base03 },
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
".cm-searchMatch": {
backgroundColor: "#72a1ff59",
outline: "1px solid #457dff"
},
".cm-searchMatch.cm-searchMatch-selected": {
backgroundColor: "#6199ff2f"
},
".cm-activeLine": { backgroundColor: highlightBackground },
".cm-selectionMatch": { backgroundColor: "#aafe661a" },
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
outline: `1px solid ${base06}`
},
".cm-gutters": {
backgroundColor: darkBackground,
color: stone,
border: "none"
},
".cm-activeLineGutter": {
backgroundColor: highlightBackground
},
".cm-foldPlaceholder": {
backgroundColor: "transparent",
border: "none",
color: "#ddd"
},
".cm-tooltip": {
border: "none",
backgroundColor: tooltipBackground
},
".cm-tooltip .cm-tooltip-arrow:before": {
borderTopColor: "transparent",
borderBottomColor: "transparent"
},
".cm-tooltip .cm-tooltip-arrow:after": {
borderTopColor: tooltipBackground,
borderBottomColor: tooltipBackground
},
".cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
backgroundColor: highlightBackground,
color: base03
}
}
}
}, { dark: true });
},
{ dark: true }
);
/**
The highlighting style for code in the Solarized Dark theme.
*/
const solarizedDarkHighlightStyle = /*@__PURE__*/HighlightStyle.define([
const solarizedDarkHighlightStyle = /*@__PURE__*/ HighlightStyle.define([
{ tag: tags.keyword, color: base_green },
{
tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName],
tag: [
tags.name,
tags.deleted,
tags.character,
tags.propertyName,
tags.macroName
],
color: base_cyan
},
{ tag: [tags.variableName], color: base05 },
{ tag: [/*@__PURE__*/tags.function(tags.variableName)], color: base_blue },
{ tag: [/*@__PURE__*/ tags.function(tags.variableName)], color: base_blue },
{ tag: [tags.labelName], color: base_magenta },
{
tag: [tags.color, /*@__PURE__*/tags.constant(tags.name), /*@__PURE__*/tags.standard(tags.name)],
tag: [
tags.color,
/*@__PURE__*/ tags.constant(tags.name),
/*@__PURE__*/ tags.standard(tags.name)
],
color: base_yellow
},
{ tag: [/*@__PURE__*/tags.definition(tags.name), tags.separator], color: base_cyan },
{
tag: [/*@__PURE__*/ tags.definition(tags.name), tags.separator],
color: base_cyan
},
{ tag: [tags.brace], color: base_magenta },
{
tag: [tags.annotation],
color: invalid
},
{
tag: [tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace],
tag: [
tags.number,
tags.changed,
tags.annotation,
tags.modifier,
tags.self,
tags.namespace
],
color: base_magenta
},
{
@ -124,32 +170,35 @@ const solarizedDarkHighlightStyle = /*@__PURE__*/HighlightStyle.define([
},
{ tag: [tags.string], color: base_yellow },
{
tag: [tags.url, tags.escape, /*@__PURE__*/tags.special(tags.string)],
tag: [tags.url, tags.escape, /*@__PURE__*/ tags.special(tags.string)],
color: base_yellow
},
{ tag: [tags.meta], color: base_red },
{ tag: [tags.comment], color: base02, fontStyle: 'italic' },
{ tag: tags.strong, fontWeight: 'bold', color: base06 },
{ tag: tags.emphasis, fontStyle: 'italic', color: base_green },
{ tag: tags.strikethrough, textDecoration: 'line-through' },
{ tag: [tags.comment], color: base02, fontStyle: "italic" },
{ tag: tags.strong, fontWeight: "bold", color: base06 },
{ tag: tags.emphasis, fontStyle: "italic", color: base_green },
{ tag: tags.strikethrough, textDecoration: "line-through" },
{
tag: tags.link,
color: base_cyan,
textDecoration: 'underline',
textUnderlinePosition: 'under'
textDecoration: "underline",
textUnderlinePosition: "under"
},
{ tag: tags.heading, fontWeight: 'bold', color: base_yellow },
{ tag: tags.heading1, fontWeight: 'bold', color: base07 },
{ tag: tags.heading, fontWeight: "bold", color: base_yellow },
{ tag: tags.heading1, fontWeight: "bold", color: base07 },
{
tag: [tags.heading2, tags.heading3, tags.heading4],
fontWeight: 'bold',
fontWeight: "bold",
color: base06
},
{
tag: [tags.heading5, tags.heading6],
color: base06
},
{ tag: [tags.atom, tags.bool, /*@__PURE__*/tags.special(tags.variableName)], color: base_magenta },
{
tag: [tags.atom, tags.bool, /*@__PURE__*/ tags.special(tags.variableName)],
color: base_magenta
},
{
tag: [tags.processingInstruction, tags.inserted, tags.contentSeparator],
color: base_red
@ -164,9 +213,6 @@ const solarizedDarkHighlightStyle = /*@__PURE__*/HighlightStyle.define([
Extension to enable the Solarized Dark theme (both the editor theme and
the highlight style).
*/
const solarizedDark = [
solarizedDarkTheme,
solarizedDarkHighlightStyle
];
const solarizedDark = [solarizedDarkTheme, solarizedDarkHighlightStyle];
export { solarizedDark, solarizedDarkHighlightStyle, solarizedDarkTheme };

@ -1,30 +1,30 @@
import { useCallback, useEffect, useState } from "react";
import { EditorView } from '@codemirror/view'
import { EditorView } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import { Extension } from "@codemirror/state";
export default function useCodeMirror(extensions: Extension[], doc: any) {
const [element, setElement] = useState<HTMLElement>();
const [element, setElement] = useState<HTMLElement>();
const ref = useCallback((node: HTMLElement | null) => {
if (!node) return;
const ref = useCallback((node: HTMLElement | null) => {
if (!node) return;
setElement(node);
}, []);
setElement(node);
}, []);
useEffect(() => {
if (!element) return;
useEffect(() => {
if (!element) return;
const view = new EditorView({
state: EditorState.create({
doc: doc,
extensions: [...extensions],
}),
parent: element,
});
const view = new EditorView({
state: EditorState.create({
doc: doc,
extensions: [...extensions]
}),
parent: element
});
return () => view?.destroy();
}, [element, extensions, doc]);
return () => view?.destroy();
}, [element, extensions, doc]);
return { ref };
return { ref };
}

@ -11,7 +11,10 @@ const ModalConfirmDelete = (props: IModalConfirmDeleteProps) => {
return (
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 outline-none focus:outline-none">
<div onClick={onHide} className="opacity-25 fixed inset-0 z-40 bg-black"></div>
<div
onClick={onHide}
className="opacity-25 fixed inset-0 z-40 bg-black"
></div>
<div className="relative w-auto my-6 mx-auto max-w-5xl z-50">
<div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none">
<div className="flex items-center justify-between px-4 py-3 border-b border-solid border-blueGray-200 rounded-t">
@ -29,7 +32,10 @@ const ModalConfirmDelete = (props: IModalConfirmDeleteProps) => {
<div className="relative px-4 py-3 flex-auto">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
<ExclamationIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div className="mt-2">
@ -54,8 +60,8 @@ const ModalConfirmDelete = (props: IModalConfirmDeleteProps) => {
type="button"
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-3 py-1 bg-red-600 text-sm font-medium text-white hover:bg-red-700 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
onHide()
onConfirm()
onHide();
onConfirm();
}}
>
Delete
@ -65,7 +71,7 @@ const ModalConfirmDelete = (props: IModalConfirmDeleteProps) => {
</div>
</div>
</div>
)
}
);
};
export default ModalConfirmDelete;

@ -5,7 +5,12 @@ const Container = (props: any) => {
<>
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3">
<label htmlFor={`container-name`} className="block text-xs font-medium text-gray-700">Name</label>
<label
htmlFor={`container-name`}
className="block text-xs font-medium text-gray-700"
>
Name
</label>
<div key={`container-name`}>
<input
type="text"
@ -20,7 +25,12 @@ const Container = (props: any) => {
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3">
<label htmlFor={`container-image`} className="block text-xs font-medium text-gray-700 mt-2">Image</label>
<label
htmlFor={`container-image`}
className="block text-xs font-medium text-gray-700 mt-2"
>
Image
</label>
<div key={`container-image`}>
<input
type="text"
@ -35,20 +45,27 @@ const Container = (props: any) => {
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3">
<label htmlFor={`container-pull-policy`} className="block text-xs font-medium text-gray-700 mt-2">Pull policy</label>
<label
htmlFor={`container-pull-policy`}
className="block text-xs font-medium text-gray-700 mt-2"
>
Pull policy
</label>
<div key={`container-pull-policy`}>
<input
type="text"
className="input-util"
name={`configuration.container.imagePullPolicy`}
value={formik.values.configuration.container.imagePullPolicy || ""}
value={
formik.values.configuration.container.imagePullPolicy || ""
}
onChange={formik.handleChange}
/>
</div>
</div>
</div>
</>
)
}
);
};
export default Container;

@ -5,11 +5,11 @@ import General from "./General";
import Container from "./Container";
import Resource from "./Resource";
import { initialValues, formatName } from "./../../../utils";
import { CallbackFunction } from "../../../types";
interface IModalProps {
onHide: any;
onAddEndpoint: Function;
onHide: CallbackFunction;
onAddEndpoint: CallbackFunction;
}
const ModalCreate = (props: IModalProps) => {
@ -18,7 +18,7 @@ const ModalCreate = (props: IModalProps) => {
const formik = useFormik({
initialValues: {
configuration: {
...initialValues(),
...initialValues()
},
key: "template",
type: "TEMPLATE",
@ -26,25 +26,36 @@ const ModalCreate = (props: IModalProps) => {
outputs: [],
config: {}
},
onSubmit: ((values, { setSubmitting }) => {
})
onSubmit: () => undefined
});
const tabs = [
{ name: 'General', href: '#', current: true, hidden: false },
{ name: 'Container', href: '#', current: false, hidden: (formik.values.configuration.type === 'container' ? false : true) },
{ name: 'Resource', href: '#', current: false, hidden: (formik.values.configuration.type === 'resource' ? false : true) }
{ name: "General", href: "#", current: true, hidden: false },
{
name: "Container",
href: "#",
current: false,
hidden: formik.values.configuration.type === "container" ? false : true
},
{
name: "Resource",
href: "#",
current: false,
hidden: formik.values.configuration.type === "resource" ? false : true
}
];
const classNames = (...classes: string[]) => {
return classes.filter(Boolean).join(' ');
}
return classes.filter(Boolean).join(" ");
};
return (
<>
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 outline-none focus:outline-none">
<div onClick={onHide} className="opacity-25 fixed inset-0 z-40 bg-black"></div>
<div
onClick={onHide}
className="opacity-25 fixed inset-0 z-40 bg-black"
></div>
<div className="relative w-auto my-6 mx-auto max-w-5xl z-50">
<div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none">
<div className="flex items-center justify-between px-4 py-3 border-b border-solid border-blueGray-200 rounded-t">
@ -68,7 +79,7 @@ const ModalCreate = (props: IModalProps) => {
id="tabs"
name="tabs"
className="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
defaultValue={tabs.find((tab) => tab.current)!.name}
defaultValue={tabs.find((tab) => tab.current)?.name}
>
{tabs.map((tab) => (
<option key={tab.name}>{tab.name}</option>
@ -79,21 +90,19 @@ const ModalCreate = (props: IModalProps) => {
<div className="hidden sm:block">
<div className="border-b border-gray-200 px-8">
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
{tabs.map((tab, index) => (
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? 'border-indigo-500 text-indigo-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
'whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm',
(tab.hidden)
? 'hidden'
: ''
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? 'page' : undefined}
onClick={e => {
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
@ -107,17 +116,11 @@ const ModalCreate = (props: IModalProps) => {
<div className="relative px-4 py-3 flex-auto">
<form onSubmit={formik.handleSubmit}>
{openTab === "General" &&
<General formik={formik} />
}
{openTab === "General" && <General formik={formik} />}
{openTab === "Container" &&
<Container formik={formik} />
}
{openTab === "Container" && <Container formik={formik} />}
{openTab === "Resource" &&
<Resource formik={formik} />
}
{openTab === "Resource" && <Resource formik={formik} />}
</form>
</div>
</div>
@ -127,7 +130,9 @@ const ModalCreate = (props: IModalProps) => {
className="btn-util"
type="button"
onClick={() => {
formik.values.configuration.name = formatName(formik.values.configuration.prettyName);
formik.values.configuration.name = formatName(
formik.values.configuration.prettyName
);
onAddEndpoint(formik.values);
formik.resetForm();
setOpenTab("General");
@ -142,6 +147,6 @@ const ModalCreate = (props: IModalProps) => {
</div>
</>
);
}
};
export default ModalCreate
export default ModalCreate;

@ -5,18 +5,18 @@ import General from "./General";
import Container from "./Container";
import Resource from "./Resource";
import { initialValues, formatName } from "./../../../utils";
import { IClientNodeItem } from "../../../types";
import { IClientNodeItem, CallbackFunction } from "../../../types";
interface IModalProps {
node: IClientNodeItem | null;
onHide: any;
onUpdateEndpoint: any;
onHide: CallbackFunction;
onUpdateEndpoint: CallbackFunction;
}
const ModalEdit = (props: IModalProps) => {
const { node, onHide, onUpdateEndpoint } = props;
const [selectedNode, setSelectedNode] = React.useState<IClientNodeItem | null>(null);
const [selectedNode, setSelectedNode] =
React.useState<IClientNodeItem | null>(null);
const [openTab, setOpenTab] = React.useState("General");
const formik = useFormik({
initialValues: {
@ -24,19 +24,32 @@ const ModalEdit = (props: IModalProps) => {
...initialValues()
}
},
onSubmit: ((values, { setSubmitting }) => {
})
onSubmit: () => undefined
});
const tabs = [
{ name: 'General', href: '#', current: true, hidden: false },
{ name: 'Container', href: '#', current: false, hidden: (formik.values.configuration.type === 'container' ? false : true) },
{ name: 'Resource', href: '#', current: false, hidden: (formik.values.configuration.type === 'resource' ? false : true) }
{
name: "General",
href: "#",
current: true,
hidden: false
},
{
name: "Container",
href: "#",
current: false,
hidden: formik.values.configuration.type === "container" ? false : true
},
{
name: "Resource",
href: "#",
current: false,
hidden: formik.values.configuration.type === "resource" ? false : true
}
];
const classNames = (...classes: string[]) => {
return classes.filter(Boolean).join(' ');
}
return classes.filter(Boolean).join(" ");
};
React.useEffect(() => {
if (node) {
@ -55,14 +68,17 @@ const ModalEdit = (props: IModalProps) => {
React.useEffect(() => {
return () => {
formik.resetForm();
}
};
}, []);
return (
<>
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 outline-none focus:outline-none">
<div onClick={onHide} className="opacity-25 fixed inset-0 z-40 bg-black"></div>
<div
onClick={onHide}
className="opacity-25 fixed inset-0 z-40 bg-black"
></div>
<div className="relative w-auto my-6 mx-auto max-w-5xl z-50">
<div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none">
<div className="flex items-center justify-between px-4 py-3 border-b border-solid border-blueGray-200 rounded-t">
@ -86,7 +102,7 @@ const ModalEdit = (props: IModalProps) => {
id="tabs"
name="tabs"
className="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
defaultValue={tabs.find((tab) => tab.current)!.name}
defaultValue={tabs.find((tab) => tab.current)?.name}
>
{tabs.map((tab) => (
<option key={tab.name}>{tab.name}</option>
@ -103,15 +119,13 @@ const ModalEdit = (props: IModalProps) => {
href={tab.href}
className={classNames(
tab.name === openTab
? 'border-indigo-500 text-indigo-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
'whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm',
(tab.hidden)
? 'hidden'
: ''
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? 'page' : undefined}
onClick={e => {
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
@ -125,17 +139,11 @@ const ModalEdit = (props: IModalProps) => {
<div className="relative px-4 py-3 flex-auto">
<form onSubmit={formik.handleSubmit}>
{openTab === "General" &&
<General formik={formik} />
}
{openTab === "General" && <General formik={formik} />}
{openTab === "Container" &&
<Container formik={formik} />
}
{openTab === "Container" && <Container formik={formik} />}
{openTab === "Resource" &&
<Resource formik={formik} />
}
{openTab === "Resource" && <Resource formik={formik} />}
</form>
</div>
</div>
@ -145,8 +153,10 @@ const ModalEdit = (props: IModalProps) => {
className="btn-util"
type="button"
onClick={() => {
let updated = { ...selectedNode };
formik.values.configuration.name = formatName(formik.values.configuration.prettyName);
const updated = { ...selectedNode };
formik.values.configuration.name = formatName(
formik.values.configuration.prettyName
);
updated.configuration = formik.values.configuration;
onUpdateEndpoint(updated);
}}
@ -160,6 +170,6 @@ const ModalEdit = (props: IModalProps) => {
</div>
</>
);
}
};
export default ModalEdit
export default ModalEdit;

@ -1,6 +1,5 @@
import React from "react";
const General = (props: any) => {
const { formik } = props;
@ -8,7 +7,12 @@ const General = (props: any) => {
<>
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3">
<label htmlFor="prettyName" className="block text-xs font-medium text-gray-700">Name</label>
<label
htmlFor="prettyName"
className="block text-xs font-medium text-gray-700"
>
Name
</label>
<div className="mt-1">
<input
id="prettyName"
@ -24,7 +28,12 @@ const General = (props: any) => {
</div>
<div className="mt-2">
<label htmlFor="about" className="block text-xs font-medium text-gray-700">Description</label>
<label
htmlFor="about"
className="block text-xs font-medium text-gray-700"
>
Description
</label>
<div className="mt-1">
<textarea
id="description"
@ -40,7 +49,12 @@ const General = (props: any) => {
<div className="grid grid-cols-3 gap-4 mt-2">
<div className="col-span-3">
<label htmlFor="templateType" className="block text-xs font-medium text-gray-700">Type</label>
<label
htmlFor="templateType"
className="block text-xs font-medium text-gray-700"
>
Type
</label>
<div className="mt-1">
<select
id="templateType"
@ -57,6 +71,6 @@ const General = (props: any) => {
</div>
</div>
</>
)
}
export default General;
);
};
export default General;

@ -1,6 +1,5 @@
import React from "react";
const Resource = (props: any) => {
const { formik } = props;
@ -8,7 +7,12 @@ const Resource = (props: any) => {
<>
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3">
<label htmlFor={`resource-action`} className="block text-xs font-medium text-gray-700">Action</label>
<label
htmlFor={`resource-action`}
className="block text-xs font-medium text-gray-700"
>
Action
</label>
<div key={`resource-action`}>
<input
type="text"
@ -23,7 +27,12 @@ const Resource = (props: any) => {
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3">
<label htmlFor={`resource-manifest`} className="block text-xs font-medium text-gray-700 mt-2">Manifest</label>
<label
htmlFor={`resource-manifest`}
className="block text-xs font-medium text-gray-700 mt-2"
>
Manifest
</label>
<textarea
id="resource-manifest"
rows={2}
@ -36,6 +45,6 @@ const Resource = (props: any) => {
</div>
</div>
</>
)
}
export default Resource;
);
};
export default Resource;

@ -1,11 +1,11 @@
import { useFormik } from "formik";
import { XIcon } from "@heroicons/react/outline";
import { serviceInitialValues, formatName } from "../../../utils";
import { CallbackFunction } from "../../../types";
interface IModalServiceProps {
onHide: any;
onAddEndpoint: Function;
onHide: CallbackFunction;
onAddEndpoint: CallbackFunction;
}
const ModalServiceCreate = (props: IModalServiceProps) => {
@ -13,7 +13,7 @@ const ModalServiceCreate = (props: IModalServiceProps) => {
const formik = useFormik({
initialValues: {
configuration: {
...serviceInitialValues(),
...serviceInitialValues()
},
key: "service",
type: "SERVICE",
@ -21,15 +21,16 @@ const ModalServiceCreate = (props: IModalServiceProps) => {
outputs: [],
config: {}
},
onSubmit: ((values, { setSubmitting }) => {
})
onSubmit: () => undefined
});
return (
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 outline-none focus:outline-none">
<div onClick={onHide} className="opacity-25 fixed inset-0 z-40 bg-black"></div>
<div
onClick={onHide}
className="opacity-25 fixed inset-0 z-40 bg-black"
></div>
<div className="relative w-auto my-6 mx-auto max-w-5xl z-50">
<div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none">
<div className="flex items-center justify-between px-4 py-3 border-b border-solid border-blueGray-200 rounded-t">
@ -47,7 +48,12 @@ const ModalServiceCreate = (props: IModalServiceProps) => {
<div className="relative px-4 py-3 flex-auto">
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3">
<label htmlFor="prettyName" className="block text-xs font-medium text-gray-700">Name</label>
<label
htmlFor="prettyName"
className="block text-xs font-medium text-gray-700"
>
Name
</label>
<div className="mt-1">
<input
id="prettyName"
@ -64,7 +70,12 @@ const ModalServiceCreate = (props: IModalServiceProps) => {
<div className="mt-2">
<div className="col-span-3">
<label htmlFor="template" className="block text-xs font-medium text-gray-700">Template</label>
<label
htmlFor="template"
className="block text-xs font-medium text-gray-700"
>
Template
</label>
<div className="mt-1">
<input
id="template"
@ -85,7 +96,9 @@ const ModalServiceCreate = (props: IModalServiceProps) => {
className="btn-util"
type="button"
onClick={() => {
formik.values.configuration.name = formatName(formik.values.configuration.prettyName);
formik.values.configuration.name = formatName(
formik.values.configuration.prettyName
);
onAddEndpoint(formik.values);
formik.resetForm();
}}
@ -98,6 +111,6 @@ const ModalServiceCreate = (props: IModalServiceProps) => {
</div>
</div>
);
}
};
export default ModalServiceCreate
export default ModalServiceCreate;

@ -3,7 +3,6 @@ import { useFormik } from "formik";
import { XIcon } from "@heroicons/react/outline";
import { serviceInitialValues, formatName } from "../../../utils";
interface IModalServiceProps {
node: any;
onHide: any;
@ -19,9 +18,7 @@ const ModalServiceEdit = (props: IModalServiceProps) => {
...serviceInitialValues()
}
},
onSubmit: ((values, { setSubmitting }) => {
})
onSubmit: () => undefined
});
React.useEffect(() => {
@ -41,14 +38,17 @@ const ModalServiceEdit = (props: IModalServiceProps) => {
React.useEffect(() => {
return () => {
formik.resetForm();
}
};
}, []);
return (
<>
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 outline-none focus:outline-none">
<div onClick={onHide} className="opacity-25 fixed inset-0 z-40 bg-black"></div>
<div
onClick={onHide}
className="opacity-25 fixed inset-0 z-40 bg-black"
></div>
<div className="relative w-auto my-6 mx-auto max-w-5xl z-50">
<div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none">
<div className="flex items-center justify-between px-4 py-3 border-b border-solid border-blueGray-200 rounded-t">
@ -66,7 +66,12 @@ const ModalServiceEdit = (props: IModalServiceProps) => {
<div className="relative px-4 py-3 flex-auto">
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3">
<label htmlFor="prettyName" className="block text-xs font-medium text-gray-700">Name</label>
<label
htmlFor="prettyName"
className="block text-xs font-medium text-gray-700"
>
Name
</label>
<div className="mt-1">
<input
id="prettyName"
@ -83,7 +88,12 @@ const ModalServiceEdit = (props: IModalServiceProps) => {
<div className="mt-2">
<div className="col-span-3">
<label htmlFor="template" className="block text-xs font-medium text-gray-700">Template</label>
<label
htmlFor="template"
className="block text-xs font-medium text-gray-700"
>
Template
</label>
<div className="mt-1">
<input
id="template"
@ -104,8 +114,10 @@ const ModalServiceEdit = (props: IModalServiceProps) => {
className="btn-util"
type="button"
onClick={() => {
let updated = { ...selectedNode };
formik.values.configuration.name = formatName(formik.values.configuration.prettyName);
const updated = { ...selectedNode };
formik.values.configuration.name = formatName(
formik.values.configuration.prettyName
);
updated.configuration = formik.values.configuration;
onUpdateEndpoint(updated);
}}
@ -119,6 +131,6 @@ const ModalServiceEdit = (props: IModalServiceProps) => {
</div>
</>
);
}
};
export default ModalServiceEdit
export default ModalServiceEdit;

@ -1,5 +1,3 @@
import { useState } from "react";
import SideBar from "../../components/global/SideBar";
import { LOCAL_STORAGE } from "../../constants";
import { authSelf } from "../../reducers";
@ -14,14 +12,16 @@ const Profile = (props: IProfileProps) => {
const logOut = () => {
localStorage.removeItem(LOCAL_STORAGE);
dispatch(authSelf(null));
}
};
return (
<>
<div className="md:pl-16 flex flex-col flex-1">
<main className="py-6">
<div className="flex justify-between px-4 sm:px-6 md:px-8">
<h1 className="text-2xl font-semibold dark:text-white text-gray-900">Profile</h1>
<h1 className="text-2xl font-semibold dark:text-white text-gray-900">
Profile
</h1>
<button
className="btn-util text-white bg-blue-600 hover:bg-blue-700 sm:w-auto"
onClick={logOut}
@ -30,9 +30,9 @@ const Profile = (props: IProfileProps) => {
</button>
</div>
<div className="grid grid-cols-1 gap-x-4 gap-y-8 px-4 py-4 sm:px-6 md:flex-row md:px-8">
{state.user &&
{state.user && (
<>
{state.user.username &&
{state.user.username && (
<div className="sm:col-span-1">
<dt className="text-sm font-medium dark:text-gray-200 text-gray-500">
username
@ -41,9 +41,9 @@ const Profile = (props: IProfileProps) => {
{state.user.username}
</dd>
</div>
}
)}
{state.user.email &&
{state.user.email && (
<div className="sm:col-span-1">
<dt className="text-sm font-medium dark:text-gray-200 text-gray-500">
email
@ -52,14 +52,14 @@ const Profile = (props: IProfileProps) => {
{state.user.email}
</dd>
</div>
}
)}
</>
}
)}
</div>
</main>
</div>
</>
)
}
);
};
export default Profile;

@ -1,12 +1,21 @@
import { useEffect, useState, useRef, useMemo } from "react";
import { useParams } from "react-router-dom";
import { debounce, Dictionary, omit } from 'lodash';
import { debounce, Dictionary, omit } from "lodash";
import YAML from "yaml";
import { PlusIcon } from "@heroicons/react/solid";
import { IProjectPayload, IClientNodeItem, IServiceNodePosition, IProject } from "../../types";
import {
IProjectPayload,
IClientNodeItem,
IServiceNodePosition,
IProject
} from "../../types";
import eventBus from "../../events/eventBus";
import { useMutation } from "react-query";
import { useProject, useUpdateProject, createProject } from "../../hooks/useProject";
import {
useProject,
useUpdateProject,
createProject
} from "../../hooks/useProject";
import useWindowDimensions from "../../hooks/useWindowDimensions";
import { flattenGraphData } from "../../utils/generators";
import { nodeLibraries } from "../../utils/data/libraries";
@ -39,36 +48,47 @@ export default function Project() {
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 [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 [connections, setConnections] = useState<[[string, string]] | []>([]);
const [projectName, setProjectName] = useState("Untitled");
const [canvasPosition, setCanvasPosition] = useState({top: 0, left: 0, scale: 1});
const [canvasPosition, setCanvasPosition] = useState({
top: 0,
left: 0,
scale: 1
});
const updateProjectMutation = useUpdateProject(uuid);
const createProjectMutation = useMutation((payload: IProjectPayload) => {
return createProject(payload)
},
{
onSuccess: (project: IProject) => {
window.location.replace(`/projects/${project.uuid}`)
const createProjectMutation = useMutation(
(payload: IProjectPayload) => {
return createProject(payload);
},
{
onSuccess: (project: IProject) => {
window.location.replace(`/projects/${project.uuid}`);
}
}
});
);
stateNodesRef.current = nodes;
stateConnectionsRef.current = connections;
const handleNameChange = (e: any) => {
setProjectName(e.target.value);
}
};
const onNodeUpdate = (positionData: IServiceNodePosition) => {
if (stateNodesRef.current) {
const node = { ...stateNodesRef.current[positionData.key], ...positionData };
const node = {
...stateNodesRef.current[positionData.key],
...positionData
};
setNodes({ ...stateNodesRef.current, [positionData.key]: node });
}
}
};
const onSave = () => {
const payload: IProjectPayload = {
@ -86,19 +106,19 @@ export default function Project() {
version: 3,
volumes: []
}
}
};
if (uuid) {
updateProjectMutation.mutate(payload);
} else {
createProjectMutation.mutate(payload);
}
}
};
const setViewHeight = () => {
let vh = window.innerHeight * 0.01;
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty("--vh", `${vh}px`);
}
};
const copy = () => {
navigator.clipboard.writeText(formattedCode);
@ -107,7 +127,7 @@ export default function Project() {
setTimeout(() => {
setCopyText("Copy");
}, 300);
}
};
useEffect(() => {
if (!data) {
@ -115,8 +135,13 @@ export default function Project() {
}
const canvasData = JSON.parse(data.data);
const nodesAsList = Object.keys(canvasData.canvas.nodes).map(k => canvasData.canvas.nodes[k]);
const clientNodeItems = getClientNodesAndConnections(nodesAsList, nodeLibraries);
const nodesAsList = Object.keys(canvasData.canvas.nodes).map(
(k) => canvasData.canvas.nodes[k]
);
const clientNodeItems = getClientNodesAndConnections(
nodesAsList,
nodeLibraries
);
setProjectName(data.name);
setNodes(clientNodeItems);
@ -124,31 +149,35 @@ export default function Project() {
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(() => {
const debouncedOnCodeChange = useMemo(
() =>
debounce((code: string) => {
//formik.setFieldValue("code", e, false);
}, 700),
[]
);
});
}, 450), []);
const debouncedOnGraphUpdate = useMemo(
() =>
debounce((graphData) => {
const flatData = flattenGraphData(graphData);
generateHttp(flatData)
.then(checkHttpStatus)
.then((data) => {
if (data["code"].length) {
for (let 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(() => undefined)
.finally(() => undefined);
}, 450),
[]
);
const onCodeUpdate = (code: string) => {
debouncedOnCodeChange(code);
@ -159,39 +188,47 @@ export default function Project() {
};
const onCanvasUpdate = (updatedCanvasPosition: any) => {
setCanvasPosition({...canvasPosition, ...updatedCanvasPosition});
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)));
const sections = flattenLibraries(nodeLibraries);
const 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) {
if (
!stateConnectionsRef.current ||
stateConnectionsRef.current.length <= 0
) {
return;
}
const _connections: [[string, string]] = [...stateConnectionsRef.current] as any;
const _connections: [[string, string]] = [
...stateConnectionsRef.current
] as any;
const existingIndex = getMatchingSetIndex(_connections, data);
if (existingIndex !== -1) {
@ -199,25 +236,27 @@ export default function Project() {
}
setConnections(_connections);
}
};
const onConnectionAttached = (data: any) => {
if (stateConnectionsRef.current && stateConnectionsRef.current.length > 0) {
const _connections: [[string, string]] = [...stateConnectionsRef.current] as any;
const _connections: [[string, string]] = [
...stateConnectionsRef.current
] as any;
const existingIndex = getMatchingSetIndex(_connections, data);
if (existingIndex === -1) {
_connections.push(data);
}
setConnections(_connections);
} else {
setConnections([data]);
setConnections([data]);
}
}
};
const onRemoveEndpoint = (node: IClientNodeItem) => {
setNodes({ ...omit(nodes, node.key) });
eventBus.dispatch("NODE_DELETED", { message: { "node": node } });
}
eventBus.dispatch("NODE_DELETED", { message: { node: node } });
};
useEffect(() => {
if (!generatedCode) {
@ -234,44 +273,42 @@ export default function Project() {
}, [language, generatedCode]);
if (!isFetching) {
return (
<>
{showModalCreateService
? <ModalServiceCreate
onHide={() => setShowModalCreateService(false)}
onAddEndpoint={(values: any) => onAddEndpoint(values)}
/>
: null
}
{nodeForEdit
? <ModalServiceEdit
node={nodeForEdit}
onHide={() => setNodeForEdit(null)}
onUpdateEndpoint={(values: any) => onUpdateEndpoint(values)}
/>
: null
}
{nodeForDelete
? <ModalConfirmDelete
onHide={() => setNodeForDelete(null)}
onConfirm={() => {
onRemoveEndpoint(nodeForDelete);
setNodeForDelete(null);
}}
/>
: null
}
<div className="md:pl-16 flex flex-col flex-1">
<div className="px-4 py-3 border-b border-gray-200">
<form
className="flex flex-col space-y-2 md:space-y-0 md:flex-row md:justify-between items-center"
autoComplete="off"
>
<input
className={`
if (!error) {
return (
<>
{showModalCreateService ? (
<ModalServiceCreate
onHide={() => setShowModalCreateService(false)}
onAddEndpoint={(values: any) => onAddEndpoint(values)}
/>
) : null}
{nodeForEdit ? (
<ModalServiceEdit
node={nodeForEdit}
onHide={() => setNodeForEdit(null)}
onUpdateEndpoint={(values: any) => onUpdateEndpoint(values)}
/>
) : null}
{nodeForDelete ? (
<ModalConfirmDelete
onHide={() => setNodeForDelete(null)}
onConfirm={() => {
onRemoveEndpoint(nodeForDelete);
setNodeForDelete(null);
}}
/>
) : null}
<div className="md:pl-16 flex flex-col flex-1">
<div className="px-4 py-3 border-b border-gray-200">
<form
className="flex flex-col space-y-2 md:space-y-0 md:flex-row md:justify-between items-center"
autoComplete="off"
>
<input
className={`
bg-gray-100
appearance-none
w-full
@ -292,95 +329,149 @@ export default function Project() {
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>
type="text"
placeholder="Untitled"
autoComplete="off"
id="name"
name="name"
onChange={handleNameChange}
value={projectName}
/>
<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">
{updateProjectMutation.isLoading && <Spinner className="w-4 h-4 text-green-300" />}
{createProjectMutation.isLoading && <Spinner className="w-4 h-4 text-green-300" />}
<span>Save</span>
</div>
</button>
</div>
</form>
</div>
<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">
{updateProjectMutation.isLoading && (
<Spinner className="w-4 h-4 text-green-300" />
)}
{createProjectMutation.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 - 64) }}>
<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 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 - 64 }}
>
<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>
<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>
<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)}
<CodeEditor
data={formattedCode}
language={language}
onChange={(e: any) => {
onCodeUpdate(e);
}}
disabled={false}
lineWrapping={false}
height={height - 64}
/>
</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>
</div>
</>
);
</>
);
} else {
return <>Something went wrong</>;
}
}
return (

@ -14,7 +14,6 @@ const PreviewBlock = (props: IPreviewBlockProps) => {
const { project } = props;
const [isHovering, setIsHovering] = useState(false);
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
const objId = project.id;
const mutation = useDeleteProject(project.uuid);
const handleMouseOver = () => {
@ -53,11 +52,9 @@ const PreviewBlock = (props: IPreviewBlockProps) => {
hover:border-gray-400
`}
>
<div className="flex-1 min-w-0">
{truncateStr(project.name, 25)}
</div>
<div className="flex-1 min-w-0">{truncateStr(project.name, 25)}</div>
{isHovering &&
{isHovering && (
<div className="flex space-x-1 absolute top-2 right-2">
<button
onClick={() => onDelete()}
@ -73,19 +70,19 @@ const PreviewBlock = (props: IPreviewBlockProps) => {
<PencilIcon className="w-3 h-3 text-gray-500 hover:text-gray-600" />
</Link>
</div>
}
)}
</div>
{showDeleteConfirmModal &&
{showDeleteConfirmModal && (
<ModalConfirmDelete
onConfirm={() => onDeleteConfirmed()}
onHide={() => {
setShowDeleteConfirmModal(false);
}}
/>
}
)}
</>
)
}
);
};
export default PreviewBlock;

@ -13,7 +13,9 @@ const Projects = () => {
<main>
<div className="py-6">
<div className="flex justify-between px-4 sm:px-6 md:px-8">
<h1 className="text-2xl font-semibold dark:text-white text-gray-900">Projects</h1>
<h1 className="text-2xl font-semibold dark:text-white text-gray-900">
Projects
</h1>
<Link
className="btn-util text-white bg-blue-600 hover:bg-blue-700 sm:w-auto"
@ -24,42 +26,51 @@ const Projects = () => {
</div>
<div className="px-4 sm:px-6 md:px-8">
{isFetching &&
{isFetching && (
<div className="flex justify-center items-center mx-auto mt-10">
<Spinner className="w-6 h-6 text-blue-600" />
</div>
}
)}
{!isFetching &&
{!isFetching && (
<div className="py-4">
{error && (
<div className="text-center">
<h3 className="mt-12 text-sm font-medium text-gray-900 dark:text-white">
Something went wrong...
</h3>
</div>
)}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{(data.results.length > 0) &&
{data.results.length > 0 &&
data.results.map((project: IProject) => {
return (
<div key={`${project.uuid}`}>
<PreviewBlock
project={project}
/>
<PreviewBlock project={project} />
</div>
)
})
}
);
})}
</div>
{(data.results.length === 0) &&
{data.results.length === 0 && (
<div className="text-center">
<h3 className="mt-12 text-sm font-medium text-gray-900 dark:text-white">Nothing here</h3>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">Get started by creating a project.</p>
<h3 className="mt-12 text-sm font-medium text-gray-900 dark:text-white">
Nothing here
</h3>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
Get started by creating a project.
</p>
</div>
}
)}
</div>
}
)}
</div>
</div>
</main>
</div>
</>
)
}
);
};
export default Projects;
export default Projects;

@ -1,16 +1,13 @@
import { MoonIcon, SunIcon } from "@heroicons/react/outline";
import { useDarkMode } from "./userDarkMode";
interface IDarkModeSwitchProps {
}
const DarkModeSwitch = (props: IDarkModeSwitchProps) => {
const DarkModeSwitch = () => {
const [isDark, setIsDark] = useDarkMode();
return (
<div className="flex flex items-center">
<button
onClick={e => setIsDark(!isDark)}
onClick={(e) => setIsDark(!isDark)}
id="theme-toggle"
type="button"
className="
@ -24,14 +21,14 @@ const DarkModeSwitch = (props: IDarkModeSwitchProps) => {
text-sm
p-2.5"
>
{isDark
? <SunIcon id="theme-toggle-light-icon" className="w-5 h-5" />
: <MoonIcon id="theme-toggle-dark-icon" className="w-5 h-5" />
}
{isDark ? (
<SunIcon id="theme-toggle-light-icon" className="w-5 h-5" />
) : (
<MoonIcon id="theme-toggle-dark-icon" className="w-5 h-5" />
)}
</button>
</div>
)
}
);
};
export default DarkModeSwitch;

@ -10,12 +10,15 @@ export function usePrefersDarkMode() {
const handler = () => setValue(mediaQuery.matches);
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}, [])
}, []);
return value;
}
export function useSafeLocalStorage(key: string, initialValue: string | undefined) {
export function useSafeLocalStorage(
key: string,
initialValue: string | undefined
) {
const [valueProxy, setValueProxy] = useState(() => {
try {
const value = window.localStorage.getItem(key);
@ -23,7 +26,7 @@ export function useSafeLocalStorage(key: string, initialValue: string | undefine
} catch {
return initialValue;
}
})
});
const setValue = (value: string) => {
try {
@ -32,7 +35,7 @@ export function useSafeLocalStorage(key: string, initialValue: string | undefine
} catch {
setValueProxy(value);
}
}
};
return [valueProxy, setValue];
}

@ -1,7 +1,5 @@
import Spinner from "./Spinner";
interface IPaginationProps {
defaultCurrent: number;
defaultPageSize: number;
@ -26,7 +24,7 @@ const Pagination = (props: IPaginationProps) => {
{loading ? <Spinner className="w-4 h-4" /> : <span>load more</span>}
</button>
</div>
)
);
};
export default Pagination;
export default Pagination;

@ -9,8 +9,18 @@ const Search = (props: ISearchProps) => {
return (
<div className="flex-1 flex">
<form className="w-full flex md:ml-0" autoComplete="off" method="post" action="">
<input autoComplete="false" name="hidden" type="text" className="hidden" />
<form
className="w-full flex md:ml-0"
autoComplete="off"
method="post"
action=""
>
<input
autoComplete="false"
name="hidden"
type="text"
className="hidden"
/>
<label htmlFor="search-field" className="sr-only">
Search
</label>
@ -29,7 +39,7 @@ const Search = (props: ISearchProps) => {
</div>
</form>
</div>
)
}
);
};
export default Search;

@ -13,14 +13,16 @@ export default function SideBar(props: ISideBarProps) {
const { pathname } = useLocation();
const { state, isAuthenticated } = props;
const projRegex = /\/projects\/?$/;
const navigation = [{
name: "Projects",
href: "/projects",
icon: BookOpenIcon,
current: (pathname.match(projRegex) ? true : false)
}];
const navigation = [
{
name: "Projects",
href: "/projects",
icon: BookOpenIcon,
current: pathname.match(projRegex) ? true : false
}
];
const classNames = (...classes: any[]) => {
return classes.filter(Boolean).join(" ")
return classes.filter(Boolean).join(" ");
};
const userName = state.user ? state.user.username : "";
@ -30,7 +32,7 @@ export default function SideBar(props: ISideBarProps) {
<div className="flex flex-col flex-grow pt-5 bg-blue-700 overflow-y-auto">
<div className="flex items-center flex-shrink-0 mx-auto">
<Link to={isAuthenticated ? "/" : "projects/new"}>
<Logo className="" />
<Logo />
</Link>
</div>
@ -41,20 +43,26 @@ export default function SideBar(props: ISideBarProps) {
key={item.name}
href={item.href}
className={classNames(
item.current ? "bg-blue-800 text-white" : "text-blue-100 hover:bg-blue-600",
item.current
? "bg-blue-800 text-white"
: "text-blue-100 hover:bg-blue-600",
"group flex items-center justify-center px-2 py-2 text-sm font-medium rounded-md"
)}
>
<item.icon className="mr-3 md:mr-0 flex-shrink-0 h-5 w-5" aria-hidden="true" />
<span className="md:hidden">
{item.name}
</span>
<item.icon
className="mr-3 md:mr-0 flex-shrink-0 h-5 w-5"
aria-hidden="true"
/>
<span className="md:hidden">{item.name}</span>
</a>
))}
</nav>
</div>
<UserMenu username={userName} current={pathname.includes("profile")} />
<UserMenu
username={userName}
current={pathname.includes("profile")}
/>
</div>
</div>
</>

@ -1,5 +1,4 @@
import React from 'react'
import React from "react";
interface ISpinnerProps {
className: string;
@ -9,11 +8,27 @@ const Spinner = (props: ISpinnerProps) => {
const { className } = props;
return (
<svg className={`animate-spin h-5 w-5 inline-block ${className}`} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
<svg
className={`animate-spin h-5 w-5 inline-block ${className}`}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="3"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
)
}
);
};
export default Spinner
export default Spinner;

@ -11,12 +11,14 @@ export default function UserMenu(props: IUserMenuProps) {
const navigate = useNavigate();
return (
<div
<div
onClick={() => {
navigate("/profile");
}}
className={`
${current ? "bg-blue-800 text-white" : "text-blue-100 hover:bg-blue-600"},
${
current ? "bg-blue-800 text-white" : "text-blue-100 hover:bg-blue-600"
},
flex border-t border-blue-800 p-4 w-full hover:cursor-pointer hover:bg-blue-600
`}
>
@ -24,14 +26,13 @@ export default function UserMenu(props: IUserMenuProps) {
<UserCircleIcon className="inline-block h-8 w-8 rounded-full text-white" />
<div className="ml-3 md:ml-0">
<p className="text-base font-medium text-white md:hidden">
{username
? <>{username}</>
: <>Log in</>
}
{username ? <>{username}</> : <>Log in</>}
</p>
<p className="text-sm font-medium text-indigo-200 group-hover:text-white md:hidden">
View profile
</p>
<p className="text-sm font-medium text-indigo-200 group-hover:text-white md:hidden">View profile</p>
</div>
</div>
</div>
)
}
);
}

@ -1,28 +1,64 @@
interface ILogoProps {
className: string;
}
const Logo = (props: ILogoProps) => {
const { className } = props;
const Logo = () => {
return (
<svg width="28px" height="28px" viewBox="0 0 152 152" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
<svg
width="28px"
height="28px"
viewBox="0 0 152 152"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<title>Nuxx</title>
<g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g
id="Page-1"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g id="Logo" transform="translate(-179.000000, -225.000000)">
<g id="nuxx-logo-copy" transform="translate(167.000000, 200.000000)">
<g id="nuxx-logo" transform="translate(98.500000, 95.000000) rotate(-60.000000) translate(-98.500000, -95.000000) translate(33.000000, 20.000000)">
<polygon id="Polygon" fillOpacity="0.15" fill="#000000" points="44 0 87.3012702 25 87.3012702 75 44 100 0.698729811 75 0.698729811 25"></polygon>
<polygon id="Polygon-Copy" fillOpacity="0.15" fill="#000000" points="87 25 130.30127 50 130.30127 100 87 125 43.6987298 100 43.6987298 50"></polygon>
<polygon id="Polygon-Copy-2" fillOpacity="0.15" fill="#000000" points="44 50 87.3012702 75 87.3012702 125 44 150 0.698729811 125 0.698729811 75"></polygon>
<text id="n" transform="translate(61.705647, 69.842201) rotate(30.000000) translate(-61.705647, -69.842201) " fontFamily="Kefa-Bold, Kefa" fontSize="71" fontWeight="bold" letterSpacing="0.492226294" fill="#FFFFFF">
<tspan x="39.6707155" y="94.8422015">n</tspan>
<g
id="nuxx-logo"
transform="translate(98.500000, 95.000000) rotate(-60.000000) translate(-98.500000, -95.000000) translate(33.000000, 20.000000)"
>
<polygon
id="Polygon"
fillOpacity="0.15"
fill="#000000"
points="44 0 87.3012702 25 87.3012702 75 44 100 0.698729811 75 0.698729811 25"
></polygon>
<polygon
id="Polygon-Copy"
fillOpacity="0.15"
fill="#000000"
points="87 25 130.30127 50 130.30127 100 87 125 43.6987298 100 43.6987298 50"
></polygon>
<polygon
id="Polygon-Copy-2"
fillOpacity="0.15"
fill="#000000"
points="44 50 87.3012702 75 87.3012702 125 44 150 0.698729811 125 0.698729811 75"
></polygon>
<text
id="n"
transform="translate(61.705647, 69.842201) rotate(30.000000) translate(-61.705647, -69.842201) "
fontFamily="Kefa-Bold, Kefa"
fontSize="71"
fontWeight="bold"
letterSpacing="0.492226294"
fill="#FFFFFF"
>
<tspan x="39.6707155" y="94.8422015">
n
</tspan>
</text>
</g>
</g>
</g>
</g>
</svg>
)
}
);
};
export default Logo;

@ -30,66 +30,86 @@ import eventBus from "../events/eventBus";
import { getConnections } from "../utils";
import { IClientNodeItem } from "../types";
import { Dictionary, isEqual } from "lodash";
import { IAnchor } from "../types";
import { IAnchor, CallbackFunction } from "../types";
export const useJsPlumb = (
nodes: Dictionary<IClientNodeItem>,
connections: Array<[string, string]>,
onGraphUpdate: Function,
onNodeUpdate: Function,
onConnectionAttached: Function,
onConnectionDetached: Function
): [(containerElement: HTMLDivElement) => void,
(zoom: number) => void,
(style: any) => void,
(node: IClientNodeItem) => void] => {
onGraphUpdate: CallbackFunction,
onNodeUpdate: CallbackFunction,
onConnectionAttached: CallbackFunction,
onConnectionDetached: CallbackFunction
): [
(containerElement: HTMLDivElement) => void,
(zoom: number) => void,
(style: any) => void,
(node: IClientNodeItem) => void
] => {
const [instance, setInstance] = useState<BrowserJsPlumbInstance>(null as any);
const containerRef = useRef<HTMLDivElement>();
const stateRef = useRef<Dictionary<IClientNodeItem>>();
const instanceRef = useRef<BrowserJsPlumbInstance>();
stateRef.current = nodes;
instanceRef.current = instance;
const containerCallbackRef = useCallback((containerElement: HTMLDivElement) => {
containerRef.current = containerElement;
}, []);
const addEndpoints = useCallback((
el: Element,
sourceAnchors: IAnchor[],
targetAnchors: IAnchor[],
maxConnections: number
) => {
sourceAnchors.forEach((x) => {
let endpoint = sourceEndpoint;
endpoint.maxConnections = maxConnections;
// arrow overlay for connector to specify
// it's dependency on another service
instance.addEndpoint(el, endpoint, {
anchor: [[1, 0.6, 1, 0], [0, 0.6, -1, 0], [0.6, 1, 0, 1], [0.6, 0, 0, -1]],
uuid: x.id,
connectorOverlays: [{
type: "PlainArrow",
options: {
width: 16,
length: 16,
location: 1,
id: "arrow"
},
}]
})
});
targetAnchors.forEach((x) => {
let endpoint = targetEndpoint;
endpoint.maxConnections = maxConnections;
const containerCallbackRef = useCallback(
(containerElement: HTMLDivElement) => {
containerRef.current = containerElement;
},
[]
);
const addEndpoints = useCallback(
(
el: Element,
sourceAnchors: IAnchor[],
targetAnchors: IAnchor[],
maxConnections: number
) => {
sourceAnchors.forEach((x) => {
const endpoint = sourceEndpoint;
endpoint.maxConnections = maxConnections;
// arrow overlay for connector to specify
// it's dependency on another service
instance.addEndpoint(el, endpoint, {
anchor: [
[1, 0.6, 1, 0],
[0, 0.6, -1, 0],
[0.6, 1, 0, 1],
[0.6, 0, 0, -1]
],
uuid: x.id,
connectorOverlays: [
{
type: "PlainArrow",
options: {
width: 16,
length: 16,
location: 1,
id: "arrow"
}
}
]
});
});
instance.addEndpoint(el, endpoint, {
anchor: [[0, 0.4, -1, 0], [0.4, 1, 0, 1], [1, 0.4, 1, 0], [0.4, 0, 0, -1]],
uuid: x.id
targetAnchors.forEach((x) => {
const endpoint = targetEndpoint;
endpoint.maxConnections = maxConnections;
instance.addEndpoint(el, endpoint, {
anchor: [
[0, 0.4, -1, 0],
[0.4, 1, 0, 1],
[1, 0.4, 1, 0],
[0.4, 0, 0, -1]
],
uuid: x.id
});
});
});
}, [instance]);
},
[instance]
);
const removeEndpoint = (node: any) => {
if (!instanceRef.current) return;
@ -102,7 +122,7 @@ export const useJsPlumb = (
instance.destroyConnector(conn);
instance.deleteConnection(conn);
});
};
}
instance.removeAllEndpoints(document.getElementById(node.key) as Element);
instance.repaintEverything();
@ -122,7 +142,7 @@ export const useJsPlumb = (
type: "Label",
options: {
label: "x",
location: .5,
location: 0.5,
id: "remove-conn",
cssClass: `
block jtk-overlay remove-conn-btn text-xs leading-normal cursor-pointer
@ -134,43 +154,50 @@ export const useJsPlumb = (
}
}
}
}
};
};
const setZoom = useCallback((zoom: number) => {
if (instance) {
instance.setZoom(zoom);
}
}, [instance]);
const setZoom = useCallback(
(zoom: number) => {
if (instance) {
instance.setZoom(zoom);
}
},
[instance]
);
const setStyle = useCallback((style: any) => {
let styles: { [key: string]: any } = {};
const currentStyle = containerRef.current?.getAttribute("style");
if (currentStyle) {
let currentStyleParts = (
currentStyle
.split(";")
.map(element => element.trim())
.filter(element => element !== '')
);
const currentStyleParts = currentStyle
.split(";")
.map((element) => element.trim())
.filter((element) => element !== "");
for (let i = 0; i < currentStyleParts.length; i++) {
const entry = currentStyleParts[i].split(':');
styles[entry.splice(0, 1)[0]] = entry.join(':').trim();
const entry = currentStyleParts[i].split(":");
styles[entry.splice(0, 1)[0]] = entry.join(":").trim();
}
}
styles = {...styles, ...style};
const styleString = (
Object.entries(styles).map(([k, v]) => `${k}:${v}`).join(';')
);
styles = { ...styles, ...style };
const styleString = Object.entries(styles)
.map(([k, v]) => `${k}:${v}`)
.join(";");
containerRef.current?.setAttribute("style", `${styleString}`);
}, []);
const onbeforeDropIntercept = (instance: BrowserJsPlumbInstance, params: BeforeDropParams) => {
const existingConnections: ConnectionSelection = instance.select({ source: params.sourceId as any, target: params.targetId as any });
const onbeforeDropIntercept = (
instance: BrowserJsPlumbInstance,
params: BeforeDropParams
) => {
const existingConnections: ConnectionSelection = instance.select({
source: params.sourceId as any,
target: params.targetId as any
});
// prevent duplicates when switching existing connections
if (existingConnections.length > 1) {
@ -178,15 +205,23 @@ export const useJsPlumb = (
}
if (existingConnections.length > 0) {
const firstConnection: Connection = {...existingConnections.get(0)} as Connection;
const firstConnection: Connection = {
...existingConnections.get(0)
} as Connection;
// special case to handle existing connections changing targets
if (firstConnection.suspendedElementId) {
onConnectionDetached([params.sourceId, firstConnection.suspendedElementId]);
onConnectionDetached([
params.sourceId,
firstConnection.suspendedElementId
]);
if (params.targetId !== firstConnection.suspendedElementId) {
const loopCheck = instance.select({ source: params.targetId as any, target: params.sourceId as any });
const loopCheck = instance.select({
source: params.targetId as any,
target: params.sourceId as any
});
if (loopCheck.length > 0) {
return false;
} else {
@ -197,13 +232,19 @@ export const useJsPlumb = (
}
// prevent duplicate connections from the same source to target
if (firstConnection.sourceId === params.sourceId && firstConnection.targetId === params.targetId) {
if (
firstConnection.sourceId === params.sourceId &&
firstConnection.targetId === params.targetId
) {
return false;
}
}
// prevent looping connections between a target and source
const loopCheck = instance.select({ source: params.targetId as any, target: params.sourceId as any });
const loopCheck = instance.select({
source: params.targetId as any,
target: params.sourceId as any
});
if (loopCheck.length > 0) {
return false;
}
@ -223,7 +264,7 @@ export const useJsPlumb = (
instance.reset();
instance.destroy();
}
};
useEffect(() => {
if (!instance) return;
@ -242,12 +283,14 @@ export const useJsPlumb = (
maxConnections
);
}
};
}
});
onGraphUpdate({
'nodes': stateRef.current,
'connections': getConnections(instance.getConnections({}, true) as Connection[])
nodes: stateRef.current,
connections: getConnections(
instance.getConnections({}, true) as Connection[]
)
});
}
}, [instance, addEndpoints, onGraphUpdate, stateRef.current]);
@ -255,13 +298,13 @@ export const useJsPlumb = (
useEffect(() => {
if (!instance) return;
let exisitngConnectionUuids = (instance.getConnections({}, true) as Connection[]).map(
(x) => x.getUuids()
);
const exisitngConnectionUuids = (
instance.getConnections({}, true) as Connection[]
).map((x) => x.getUuids());
connections.forEach((x) => {
let c = exisitngConnectionUuids.find((y) => {
return isEqual([`op_${x[0]}`, `ip_${x[1]}`], y)
const c = exisitngConnectionUuids.find((y) => {
return isEqual([`op_${x[0]}`, `ip_${x[1]}`], y);
});
if (!c) {
@ -280,37 +323,61 @@ export const useJsPlumb = (
});
jsPlumbInstance.bind(EVENT_DRAG_START, function (params: DragStartPayload) {
eventBus.dispatch("EVENT_DRAG_START", { message: { "id": params.el.id } });
eventBus.dispatch("EVENT_DRAG_START", { message: { id: params.el.id } });
});
jsPlumbInstance.bind(EVENT_DRAG_STOP, function (params: DragStartPayload) {
eventBus.dispatch("EVENT_DRAG_STOP", { message: {"id": params.el.id} });
eventBus.dispatch("EVENT_DRAG_STOP", { message: { id: params.el.id } });
});
jsPlumbInstance.bind(INTERCEPT_BEFORE_DROP, function (params: BeforeDropParams) {
return onbeforeDropIntercept(jsPlumbInstance, params);
});
jsPlumbInstance.bind(
INTERCEPT_BEFORE_DROP,
function (params: BeforeDropParams) {
return onbeforeDropIntercept(jsPlumbInstance, params);
}
);
jsPlumbInstance.bind(EVENT_CONNECTION_DETACHED, function (this: BrowserJsPlumbInstance, params: ConnectionDetachedParams) {
onConnectionDetached([params.sourceId, params.targetId]);
jsPlumbInstance.bind(
EVENT_CONNECTION_DETACHED,
function (
this: BrowserJsPlumbInstance,
params: ConnectionDetachedParams
) {
onConnectionDetached([params.sourceId, params.targetId]);
onGraphUpdate({
nodes: stateRef.current,
connections: getConnections(
this.getConnections({}, true) as Connection[]
)
});
}
);
onGraphUpdate({
'nodes': stateRef.current,
'connections': getConnections(this.getConnections({}, true) as Connection[])
});
});
jsPlumbInstance.bind(
EVENT_CONNECTION,
function (
this: BrowserJsPlumbInstance,
params: ConnectionEstablishedParams
) {
if (
!Object.prototype.hasOwnProperty.call(
params.connection.overlays,
"remove-conn"
)
) {
params.connection.addOverlay(getOverlayObject(this));
onConnectionAttached([params.sourceId, params.targetId]);
}
jsPlumbInstance.bind(EVENT_CONNECTION, function (this: BrowserJsPlumbInstance, params: ConnectionEstablishedParams) {
if (!params.connection.overlays.hasOwnProperty("remove-conn")) {
params.connection.addOverlay(getOverlayObject(this));
onConnectionAttached([params.sourceId, params.targetId]);
onGraphUpdate({
nodes: stateRef.current,
connections: getConnections(
this.getConnections({}, true) as Connection[]
)
});
}
onGraphUpdate({
'nodes': stateRef.current,
'connections': getConnections(this.getConnections({}, true) as Connection[])
});
});
);
jsPlumbInstance.bind(EVENT_DRAG_STOP, (params: DragStopPayload) => {
params.elements.forEach((el) => {
@ -324,9 +391,12 @@ export const useJsPlumb = (
});
});
jsPlumbInstance.bind(EVENT_CONNECTION_DBL_CLICK, (connection: Connection) => {
jsPlumbInstance.deleteConnection(connection);
});
jsPlumbInstance.bind(
EVENT_CONNECTION_DBL_CLICK,
(connection: Connection) => {
jsPlumbInstance.deleteConnection(connection);
}
);
/*
jsPlumbInstance.bind("drag:move", function (info: any) {
@ -347,4 +417,4 @@ export const useJsPlumb = (
}, []);
return [containerCallbackRef, setZoom, setStyle, removeEndpoint];
}
};

@ -1,3 +1,3 @@
export const API_SERVER_URL = process.env.REACT_APP_API_SERVER;
export const PROJECTS_FETCH_LIMIT = 300;
export const LOCAL_STORAGE = 'NuxxLocalStorage';
export const LOCAL_STORAGE = "NuxxLocalStorage";

@ -1,13 +1,22 @@
const eventBus = {
on(event: string, callback: { (data: any): void; (data: any): void; (arg: any): any; }) {
on(
event: string,
callback: { (data: any): void; (data: any): void; (arg: any): any }
) {
document.addEventListener(event, (e) => callback(e));
},
dispatch(event: string, data: { message: { id: string; } | { id: string; } | { node: any }; }) {
dispatch(
event: string,
data: { message: { id: string } | { id: string } | { node: any } }
) {
document.dispatchEvent(new CustomEvent(event, { detail: data }));
},
remove(event: string, callback: { (): void; (this: Document, ev: any): any; }) {
remove(
event: string,
callback: { (): void; (this: Document, ev: any): any }
) {
document.removeEventListener(event, callback);
},
}
};
export default eventBus;
export default eventBus;

@ -1,11 +1,10 @@
import { LOCAL_STORAGE } from "../constants"
import { LOCAL_STORAGE } from "../constants";
export const useLocalStorageAuth = () => {
const localStorageData = localStorage.getItem(LOCAL_STORAGE);
if (localStorageData) {
const authData = JSON.parse(localStorageData);
return authData
return authData;
}
}
};

@ -1,15 +1,15 @@
import { useMemo } from "react"
import { LOCAL_STORAGE } from "../constants"
import { useMemo } from "react";
import { LOCAL_STORAGE } from "../constants";
const useLocalStorageJWTKeys = () => {
const jwtKeys = localStorage.getItem(LOCAL_STORAGE)
const jwtKeys = localStorage.getItem(LOCAL_STORAGE);
return useMemo(() => {
if (jwtKeys) {
return JSON.parse(jwtKeys)
return JSON.parse(jwtKeys);
}
return null
}, [jwtKeys])
}
return null;
}, [jwtKeys]);
};
export default useLocalStorageJWTKeys
export default useLocalStorageJWTKeys;

@ -1,20 +1,20 @@
import axios from "axios"
import _ from "lodash"
import { useQuery, useMutation, useQueryClient } from "react-query"
import { API_SERVER_URL } from "../constants"
import { getLocalStorageJWTKeys } from "../utils"
import { IProject, IProjectPayload } from "../types"
import useLocalStorageJWTKeys from "./useLocalStorageJWTKeys"
import axios from "axios";
import _ from "lodash";
import { useQuery, useMutation, useQueryClient } from "react-query";
import { API_SERVER_URL } from "../constants";
import { getLocalStorageJWTKeys } from "../utils";
import { IProject, IProjectPayload } from "../types";
import useLocalStorageJWTKeys from "./useLocalStorageJWTKeys";
interface IProjectsReturn {
count: number
next: string | null
previous: string | null
results: IProject[]
count: number;
next: string | null;
previous: string | null;
results: IProject[];
}
export const createProject = async (project: IProjectPayload) => {
const jwtKeys = getLocalStorageJWTKeys()
const jwtKeys = getLocalStorageJWTKeys();
const requestConfig = {
method: "post",
url: `${API_SERVER_URL}/projects/`,
@ -22,42 +22,42 @@ export const createProject = async (project: IProjectPayload) => {
"Content-Type": "application/json"
},
data: project
}
};
if (jwtKeys) {
requestConfig.headers = {
...requestConfig.headers,
...{ Authorization: `Bearer ${jwtKeys.access_token}` }
}
};
}
const response = await axios(requestConfig)
return response.data
}
const response = await axios(requestConfig);
return response.data;
};
const deleteProjectByUuid = async (uuid: string) => {
const jwtKeys = getLocalStorageJWTKeys()
const jwtKeys = getLocalStorageJWTKeys();
const requestConfig = {
method: "delete",
url: `${API_SERVER_URL}/projects/${uuid}/`,
headers: {
"Content-Type": "application/json"
}
}
};
if (jwtKeys) {
requestConfig.headers = {
...requestConfig.headers,
...{ Authorization: `Bearer ${jwtKeys.access_token}` }
}
};
}
const response = await axios(requestConfig)
return response.data
}
const response = await axios(requestConfig);
return response.data;
};
const updateProjectByUuid = async (uuid: string, data: string) => {
const jwtKeys = getLocalStorageJWTKeys()
const jwtKeys = getLocalStorageJWTKeys();
const requestConfig = {
method: "put",
url: `${API_SERVER_URL}/projects/${uuid}/`,
@ -65,27 +65,27 @@ const updateProjectByUuid = async (uuid: string, data: string) => {
"Content-Type": "application/json"
},
data: data
}
};
if (jwtKeys) {
requestConfig.headers = {
...requestConfig.headers,
...{ Authorization: `Bearer ${jwtKeys.access_token}` }
}
};
}
const response = await axios(requestConfig)
return response.data
}
const response = await axios(requestConfig);
return response.data;
};
export const useProject = (uuid: string | undefined) => {
const jwtKeys = useLocalStorageJWTKeys()
const jwtKeys = useLocalStorageJWTKeys();
return useQuery(
["projects", uuid],
async () => {
if (!uuid) {
return
return;
}
const requestConfig = {
@ -94,71 +94,71 @@ export const useProject = (uuid: string | undefined) => {
headers: {
"Content-Type": "application/json"
}
}
};
if (jwtKeys) {
requestConfig.headers = {
...requestConfig.headers,
...{ Authorization: `Bearer ${jwtKeys.access_token}` }
}
};
}
return (await axios(requestConfig)).data
return (await axios(requestConfig)).data;
},
{
staleTime: Infinity
}
)
}
);
};
export const useUpdateProject = (uuid: string | undefined) => {
const queryClient = useQueryClient()
const queryClient = useQueryClient();
return useMutation(
async (projectData: IProjectPayload) => {
if (!uuid) {
return
return;
}
try {
const data = await updateProjectByUuid(
uuid,
JSON.stringify(projectData)
)
return data
);
return data;
} catch (err: any) {
if (err.response.status === 404) {
console.error("Resource could not be found!")
// console.error("Resource could not be found!");
} else {
console.error(err.message)
// console.error(err.message);
}
}
},
{
onSuccess: projectData => {
queryClient.setQueryData(["projects", uuid], projectData)
onSuccess: (projectData) => {
queryClient.setQueryData(["projects", uuid], projectData);
}
}
)
}
);
};
export const useDeleteProject = (uuid: string | undefined) => {
const queryClient = useQueryClient()
const queryClient = useQueryClient();
return useMutation(
async () => {
if (!uuid) {
return
return;
}
try {
const data = await deleteProjectByUuid(uuid)
return data
const data = await deleteProjectByUuid(uuid);
return data;
} catch (err: any) {
if (err.response.status === 404) {
console.error("Resource could not be found!")
// console.error("Resource could not be found!");
} else {
console.error(err.message)
// console.error(err.message);
}
}
},
@ -167,20 +167,17 @@ export const useDeleteProject = (uuid: string | undefined) => {
// could just invalidate the query here and refetch everything
// queryClient.invalidateQueries(['projects']);
queryClient.cancelQueries("projects")
queryClient.cancelQueries("projects");
const previousProjects = queryClient.getQueryData(
"projects"
) as IProjectsReturn
const filtered = _.filter(
previousProjects.results,
(project, index) => {
return project.uuid !== uuid
}
)
previousProjects.count = filtered.length
previousProjects.results = filtered
queryClient.setQueryData("projects", previousProjects)
) as IProjectsReturn;
const filtered = _.filter(previousProjects.results, (project) => {
return project.uuid !== uuid;
});
previousProjects.count = filtered.length;
previousProjects.results = filtered;
queryClient.setQueryData("projects", previousProjects);
}
}
)
}
);
};

@ -1,21 +1,21 @@
import axios from "axios"
import axios from "axios";
import { useQuery } from "react-query";
import { API_SERVER_URL, PROJECTS_FETCH_LIMIT } from "../constants";
import { API_SERVER_URL } from "../constants";
import { getLocalStorageJWTKeys } from "../utils";
const fetchProjects = async () => {
const jwtKeys = getLocalStorageJWTKeys();
const response = await axios({
method: 'get',
method: "get",
url: `${API_SERVER_URL}/projects/`,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${jwtKeys.access_token}`
Authorization: `Bearer ${jwtKeys.access_token}`
}
});
return response.data;
}
};
export const useProjects = () => {
return useQuery(
@ -26,5 +26,5 @@ export const useProjects = () => {
{
staleTime: Infinity
}
)
}
);
};

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect } from "react";
function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window;
@ -9,15 +9,17 @@ function getWindowDimensions() {
}
export default function useWindowDimensions() {
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
const [windowDimensions, setWindowDimensions] = useState(
getWindowDimensions()
);
useEffect(() => {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowDimensions;

@ -1,6 +1,6 @@
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from 'react-router-dom';
import { BrowserRouter as Router } from "react-router-dom";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

@ -6,10 +6,14 @@ export type ProtectedRouteProps = {
outlet: JSX.Element;
};
export default function ProtectedRoute({ isAuthenticated, authenticationPath, outlet }: ProtectedRouteProps) {
export default function ProtectedRoute({
isAuthenticated,
authenticationPath,
outlet
}: ProtectedRouteProps) {
if (isAuthenticated) {
return outlet;
} else {
return <Navigate to={{ pathname: authenticationPath }} />;
}
};
}

@ -16,4 +16,4 @@ const useOutsideClick = (ref: any, callback: any) => {
});
};
export default useOutsideClick;
export default useOutsideClick;

@ -1,33 +1,32 @@
const AUTH_LOGIN_SUCCESS = "auth-login-success";
const AUTH_LOGOUT_SUCCESS = "auth-logout-success";
const AUTH_SELF = "auth-self"
const AUTH_SELF = "auth-self";
export const initialState = {
user: {}
}
};
export const reducer = (state: any, action: any) => {
switch (action.type) {
case AUTH_LOGIN_SUCCESS:
return {
...state,
user: { ...action.payload.user }
}
};
case AUTH_SELF:
return {
...state,
user: { ...action.payload }
}
};
case AUTH_LOGOUT_SUCCESS:
return {
...state,
user: null
}
};
default:
throw new Error()
throw new Error();
}
}
};
export const authLoginSuccess = (data: any) => ({
type: AUTH_LOGIN_SUCCESS,
@ -35,7 +34,7 @@ export const authLoginSuccess = (data: any) => ({
});
export const authLogoutSuccess = () => ({
type: AUTH_LOGOUT_SUCCESS,
type: AUTH_LOGOUT_SUCCESS
});
export const authSelf = (data: any) => ({

@ -1,8 +1,8 @@
import { ReportHandler } from 'web-vitals';
import { ReportHandler } from "web-vitals";
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);

@ -1,7 +1,12 @@
import { API_SERVER_URL } from "../constants";
import { getLocalStorageJWTKeys } from "./utils";
export const signup = (username: string, email: string, password1: string, password2: string) =>
export const signup = (
username: string,
email: string,
password1: string,
password2: string
) =>
fetch(`${API_SERVER_URL}/auth/registration/`, {
method: "POST",
headers: {
@ -25,14 +30,14 @@ export const self = () => {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${jwtKeys.access_token}`
Authorization: `Bearer ${jwtKeys.access_token}`
}
})
}
});
};
export const refresh = () => {
const jwtKeys = getLocalStorageJWTKeys();
const body = { "refresh": jwtKeys.refresh_token };
const body = { refresh: jwtKeys.refresh_token };
return fetch(`${API_SERVER_URL}/auth/token/refresh/`, {
method: "POST",
@ -40,5 +45,5 @@ export const refresh = () => {
"Content-Type": "application/json"
},
body: JSON.stringify(body)
})
}
});
};

@ -9,4 +9,4 @@ export const generateHttp = (data: IGeneratePayload) => {
},
body: JSON.stringify(data)
});
}
};

@ -8,7 +8,7 @@ export const checkHttpStatus = (response: any) => {
}
throw response;
}
};
export const checkHttpSuccess = (response: any) => {
if ([200, 201, 202].includes(response.status)) {
@ -20,4 +20,4 @@ export const checkHttpSuccess = (response: any) => {
}
throw response;
}
};

@ -1,11 +1,11 @@
import { LOCAL_STORAGE } from "../constants";
export const getLocalStorageJWTKeys = () => {
let jwtKeys = localStorage.getItem(LOCAL_STORAGE);
const jwtKeys = localStorage.getItem(LOCAL_STORAGE);
if (jwtKeys) {
return JSON.parse(jwtKeys);
}
return null;
}
};

@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
import "@testing-library/jest-dom";

@ -2,12 +2,14 @@ import { AnchorId } from "@jsplumb/common";
import { Dictionary } from "lodash";
import { NodeGroupType } from "./enums";
export type CallbackFunction = (...args: any[]) => any;
export interface IServiceNodePosition {
key: string;
position: {
left: number;
top: number;
}
};
}
export interface IProject {
@ -89,7 +91,7 @@ export interface IAnchor {
export interface IService {
name: string;
labels: {}
labels: any;
}
export interface IProjectPayload {
@ -101,16 +103,16 @@ export interface IProjectPayload {
left: number;
scale: number;
};
nodes: {};
nodes: any;
connections: any;
};
configs: [];
networks: [];
secrets: [];
services: {};
services: any;
version: number;
volumes: [];
}
};
}
export interface IGeneratePayload {
@ -122,5 +124,5 @@ export interface IGeneratePayload {
connections: [[string, string]];
version: number;
volumes: [];
}
}
};
}

@ -1,38 +1,34 @@
import { useEffect } from "react";
/**
* Use it from the component that needs outside clicks.
*
*
* import { useClickOutside } from "../../utils/clickOutside";
*
*
* const drop = createRef<HTMLDivElement>();
* useClickOutside(drop, () => {
* // do stuff...
* });
*
* @param ref
* @param onClickOutside
*
* @param ref
* @param onClickOutside
*/
export const useClickOutside = (ref: any, onClickOutside: any) => {
useEffect(
() => {
const listener = (event: any) => {
if (!ref.current || ref.current.contains(event.target)) {
return
}
onClickOutside(event)
useEffect(() => {
const listener = (event: any) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
document.addEventListener("mousedown", listener)
document.addEventListener("touchstart", listener)
onClickOutside(event);
};
return () => {
document.removeEventListener("mousedown", listener)
document.removeEventListener("touchstart", listener)
}
},
[ref, onClickOutside]
)
}
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
}, [ref, onClickOutside]);
};

@ -1,7 +1,6 @@
import { NodeGroupType } from "../../types/enums";
import { INodeGroup } from "../../types";
export const nodeLibraries: INodeGroup[] = [
{
Id: 1,

@ -1 +1,2 @@
export const ServiceNodeConfiguration = '{"prettyName":"","name":"","key":"service","type":"SERVICE","inputs":["op_source"],"outputs":[]}';
export const ServiceNodeConfiguration =
'{"prettyName":"","name":"","key":"service","type":"SERVICE","inputs":["op_source"],"outputs":[]}';

@ -1 +1 @@
export const StartConfigString = '[]';
export const StartConfigString = "[]";

@ -1,23 +1,19 @@
import {
IClientNodeItem,
IService,
IGeneratePayload,
} from "../types";
import { IClientNodeItem, IService, IGeneratePayload } from "../types";
import { Dictionary } from "lodash";
const getServices = (graphNodes: Dictionary<IClientNodeItem>): IService[] => {
let ret: IService[] = [];
const ret: IService[] = [];
for (const [, value] of Object.entries(graphNodes)) {
ret.push({
"name": value.configuration.name,
"labels": {
"key": value.key
name: value.configuration.name,
labels: {
key: value.key
}
});
}
return ret;
}
};
export const flattenGraphData = (graphData: any): IGeneratePayload => {
const nodes = graphData["nodes"];
@ -38,4 +34,4 @@ export const flattenGraphData = (graphData: any): IGeneratePayload => {
});
return base;
}
};

@ -33,7 +33,10 @@ interface IServiceConf {
template: string;
}
export function ensure<T>(argument: T | undefined | null, message: string = 'This value was promised to be there.'): T {
export function ensure<T>(
argument: T | undefined | null,
message = "This value was promised to be there."
): T {
if (argument === undefined || argument === null) {
throw new TypeError(message);
}
@ -41,43 +44,34 @@ export function ensure<T>(argument: T | undefined | null, message: string = 'Thi
return argument;
}
export const parseSingleNode = (
configurationStr: string
): IServiceNodeItem => {
export const parseSingleNode = (configurationStr: string): IServiceNodeItem => {
let node: IServiceNodeItem = {} as IServiceNodeItem;
let configurationObj: any = null;
try {
configurationObj = JSON.parse(configurationStr);
} catch (err) { }
const configurationObj = JSON.parse(configurationStr);
if (isPlainObject(configurationObj)) {
node = configurationObj;
}
return node;
}
};
export const formatName = (name: string): string => {
let regExpr = /[^a-zA-Z0-9]/g;
const regExpr = /[^a-zA-Z0-9]/g;
return name.replace(regExpr, "-").toLowerCase();
}
};
export const parseConfiguration = (
configurationStr: string
): IServiceNodeItem[] => {
let nodes: IServiceNodeItem[] = [];
let configurationObj: any = null;
try {
configurationObj = JSON.parse(configurationStr);
} catch (err) { }
const configurationObj = JSON.parse(configurationStr);
if (isPlainObject(configurationObj)) {
nodes = flattenDeep(values(configurationObj));
}
if (isArray(configurationObj)) {
nodes = configurationObj
nodes = configurationObj;
}
nodes.forEach((node) => {
@ -87,20 +81,20 @@ export const parseConfiguration = (
});
return nodes;
}
};
export const flattenLibraries = (
sections: INodeGroup[]
): INodeLibraryItem[] => {
return flattenDeep(sections.map((x) => x.NodeTypes));
}
};
const getEndPointUuids = (
key: string,
type: "ip" | "op",
_count: string | number
): string[] => {
let count = parseInt(_count as string);
const count = parseInt(_count as string);
return range(0, count).map((x) => {
if (count === 1) {
@ -111,17 +105,19 @@ const getEndPointUuids = (
return `${type}_${x}_${key}`;
}
});
}
};
export const attachUUID = (key: string): string => {
const v4 = new RegExp(/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/i);
const v4 = new RegExp(
/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/i
);
if (key.match(v4)) {
return key;
}
return key + "-" + uuidv4();
}
};
export const getClientNodeItem = (
nodeItem: IServiceNodeItem,
@ -140,10 +136,12 @@ export const getClientNodeItem = (
},
outputs: getEndPointUuids(uniqueKey, "op", library.NoOutputs)
};
}
};
export const getConnections = (connections: Connection[]): Array<[string, string]> => {
let ret: Array<[string, string]> = [];
export const getConnections = (
connections: Connection[]
): Array<[string, string]> => {
const ret: Array<[string, string]> = [];
/*
connections.forEach((x) => {
@ -168,7 +166,7 @@ export const getConnections = (connections: Connection[]): Array<[string, string
});
return ret;
}
};
export const getClientNodesAndConnections = (
nodeItems: IServiceNodeItem[],
@ -178,18 +176,21 @@ export const getClientNodesAndConnections = (
return {};
}
let libraries = flattenLibraries(sections);
let clientItems = nodeItems.map((x) => {
return getClientNodeItem(x, ensure(libraries.find((l) => l.Type === x.type)));
const libraries = flattenLibraries(sections);
const clientItems = nodeItems.map((x) => {
return getClientNodeItem(
x,
ensure(libraries.find((l) => l.Type === x.type))
);
});
return keyBy(clientItems, (x) => x.key);
}
};
export const getNodeKeyFromConnectionId = (uuid: string) => {
let key = uuid.substr(uuid.lastIndexOf("_") + 1);
const key = uuid.substr(uuid.lastIndexOf("_") + 1);
return key;
}
};
export const initialValues = (): IConf => {
return {
@ -202,23 +203,24 @@ export const initialValues = (): IConf => {
image: "",
imagePullPolicy: ""
}
}
}
};
};
export const serviceInitialValues = (): IServiceConf => {
return {
prettyName: "Unnamed",
name: "unnamed",
template: ""
}
}
};
};
export const toaster = (message: string, type: string) => {
const toastConfig = {
duration: 3000,
position: 'bottom-right',
position: "bottom-right",
style: {},
className: 'text-sm rounded-md text-gray-600 bg-white dark:text-white dark:bg-gray-600'
className:
"text-sm rounded-md text-gray-600 bg-white dark:text-white dark:bg-gray-600"
};
if (type === "error") {
@ -228,26 +230,29 @@ export const toaster = (message: string, type: string) => {
if (type === "success") {
toast.success(message, toastConfig as any);
}
}
};
export const truncateStr = (str: string, length: number) => {
if (str.length > length) {
return str.slice(0, length) + '...'
return str.slice(0, length) + "...";
}
return str
}
return str;
};
export const getMatchingSetIndex = (setOfSets: [[string, string]], findSet: [string, string]): number => {
export const getMatchingSetIndex = (
setOfSets: [[string, string]],
findSet: [string, string]
): number => {
return setOfSets.findIndex((set) => set.toString() === findSet.toString());
}
};
export const getLocalStorageJWTKeys = () => {
let jwtKeys = localStorage.getItem(LOCAL_STORAGE);
const jwtKeys = localStorage.getItem(LOCAL_STORAGE);
if (jwtKeys) {
return JSON.parse(jwtKeys);
}
return null;
}
};

@ -3,27 +3,26 @@ import { BezierConnector } from "@jsplumb/connector-bezier";
import { AnchorId, PaintStyle } from "@jsplumb/common";
import { BrowserJsPlumbDefaults } from "@jsplumb/browser-ui";
const connectorPaintStyle: PaintStyle = {
strokeWidth: 2,
stroke: "#61B7CF"
}
};
const connectorHoverStyle: PaintStyle = {
strokeWidth: 3,
stroke: "#216477"
}
};
const endpointHoverStyle: PaintStyle = {
fill: "#216477",
stroke: "#216477"
}
};
export const defaultOptions: BrowserJsPlumbDefaults = {
dragOptions: {
cursor: "move"
}
}
};
export const sourceEndpoint: EndpointOptions = {
endpoint: {
@ -40,7 +39,7 @@ export const sourceEndpoint: EndpointOptions = {
source: true,
connector: {
type: BezierConnector.type,
options:{
options: {
curviness: 50
}
},
@ -48,7 +47,7 @@ export const sourceEndpoint: EndpointOptions = {
hoverPaintStyle: endpointHoverStyle,
connectorHoverStyle: connectorHoverStyle,
maxConnections: -1
}
};
export const targetEndpoint: EndpointOptions = {
endpoint: {
@ -65,7 +64,7 @@ export const targetEndpoint: EndpointOptions = {
hoverPaintStyle: endpointHoverStyle,
maxConnections: -1,
target: true
}
};
export const inputAnchors: AnchorId[] = ["TopLeft", "BottomLeft", "Left"];
export const outputAnchors: AnchorId[] = ["TopRight", "BottomRight", "Right"];

@ -12,16 +12,14 @@ const nodeHeight = 60;
export const getHierarchyTree = (
nodes: IServiceNodeItem[]
): d3.HierarchyPointNode<INodeItemWithParent> => {
let data = nodes.map(
(node): INodeItemWithParent => {
return {
...node,
parent: node.inputs[0] ? getNodeKeyFromConnectionId(node.inputs[0]) : ""
};
}
);
const data = nodes.map((node): INodeItemWithParent => {
return {
...node,
parent: node.inputs[0] ? getNodeKeyFromConnectionId(node.inputs[0]) : ""
};
});
let parents = data.filter((x) => !x.parent);
const parents = data.filter((x) => !x.parent);
if (parents.length > 1) {
parents.forEach((x) => {
@ -33,7 +31,7 @@ export const getHierarchyTree = (
} as INodeItemWithParent);
}
let hierarchy = d3
const hierarchy = d3
.stratify<INodeItemWithParent>()
.id(function (d: INodeItemWithParent) {
return d.key;
@ -42,18 +40,18 @@ export const getHierarchyTree = (
return d.parent;
})(data);
let tree = d3.tree<INodeItemWithParent>().nodeSize([nodeHeight, nodeWidth])(
const tree = d3.tree<INodeItemWithParent>().nodeSize([nodeHeight, nodeWidth])(
hierarchy
);
return tree;
}
};
export const getNodesPositions = (
nodes: IServiceNodeItem[]
): [IServiceNodeItem[], number, number] => {
let nodeWithPosition: IServiceNodeItem[] = [];
let tree = getHierarchyTree(nodes);
const nodeWithPosition: IServiceNodeItem[] = [];
const tree = getHierarchyTree(nodes);
let x0 = Infinity;
let x1 = -x0;
@ -62,13 +60,16 @@ export const getNodesPositions = (
if (d.x < x0) x0 = d.x;
});
let descendants = tree.descendants();
const descendants = tree.descendants();
descendants.forEach((x) => {
if (x.data.key !== "root_parent") {
nodeWithPosition.push({ ...x.data, position: { left: x.y, top: x.x + nodeHeight } });
nodeWithPosition.push({
...x.data,
position: { left: x.y, top: x.x + nodeHeight }
});
}
});
return [nodeWithPosition, 0, 0];
}
};

Loading…
Cancel
Save