Feature overhaul
BIN
app/.DS_Store
vendored
20
app/.eslintrc.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "airbnb",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"comma-dangle": ["error", {
|
||||
"arrays": "never",
|
||||
"objects": "always",
|
||||
"imports": "never",
|
||||
"exports": "never",
|
||||
"functions": "never"
|
||||
}]
|
||||
}
|
||||
}
|
||||
24
app/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -1 +0,0 @@
|
||||
export default '';
|
||||
@@ -1,3 +0,0 @@
|
||||
import idObj from 'identity-obj-proxy';
|
||||
|
||||
export default idObj;
|
||||
@@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import App from '../src/App';
|
||||
import HelloWorld from '../src/components/hello-world';
|
||||
|
||||
describe('<App />', () => {
|
||||
const wrap = mount(<App />);
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrap.find(App).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('contains HelloWorld component', () => {
|
||||
expect(wrap.find(HelloWorld).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
module.exports = function(api) {
|
||||
const babelEnv = api.env();
|
||||
api.cache(true);
|
||||
|
||||
const presets = [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
esmodules: true,
|
||||
},
|
||||
corejs: '3.0.0',
|
||||
useBuiltIns: 'usage',
|
||||
},
|
||||
],
|
||||
'@babel/preset-react',
|
||||
];
|
||||
const plugins = [
|
||||
'@babel/transform-react-constant-elements',
|
||||
'transform-react-remove-prop-types',
|
||||
'transform-react-pure-class-to-function',
|
||||
'@babel/plugin-transform-runtime',
|
||||
'react-hot-loader/babel',
|
||||
|
||||
// Stage 2 https://github.com/babel/babel/tree/master/packages/babel-preset-stage-2
|
||||
['@babel/plugin-proposal-decorators', { legacy: true }],
|
||||
'@babel/plugin-proposal-function-sent',
|
||||
'@babel/plugin-proposal-export-namespace-from',
|
||||
'@babel/plugin-proposal-numeric-separator',
|
||||
'@babel/plugin-proposal-throw-expressions',
|
||||
|
||||
// Stage 3
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-import-meta',
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
'@babel/plugin-proposal-json-strings',
|
||||
];
|
||||
|
||||
if (babelEnv === 'production') {
|
||||
plugins.push(['@babel/plugin-transform-react-inline-elements']);
|
||||
}
|
||||
|
||||
return {
|
||||
presets,
|
||||
plugins,
|
||||
};
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
body,html,#app{margin:0px;padding:0px;position:fixed;top:0;bottom:0;left:0;right:0;font-family:"Alegreya Sans SC"}
|
||||
|
||||
.menu{position:fixed;bottom:20px;right:25px}.menu .row{display:flex;flex-direction:row}.menu .row .menu-item{width:40px;padding-right:10px;cursor:pointer}.menu .row .menu-item img{width:40px}.menu .row .obs{width:40px;height:40px;border-radius:5px;background-color:#46a0f5}.menu .row .obs div{padding-top:10px;padding-left:7px;color:#e1e3e6;cursor:pointer}
|
||||
|
||||
.top-menu{position:fixed;top:20px;right:20px}.top-menu .row{display:flex;flex-direction:row}.top-menu .row .menu-item-small{width:20px;height:20px}.top-menu .row .menu-item-medium{width:25px;height:20px}.top-menu .row .json-btn{border-radius:5px;cursor:pointer;padding:5px 4px 5px 11px;color:#fff;font-weight:bold;background-color:#46a0f5}.top-menu .row .json-paste-btn{border-radius:5px;cursor:pointer;padding:5px 4px 5px 8px;margin-right:10px;color:#fff;font-weight:bold;background-color:#46a0f5}.top-menu .row .reset-btn{border-radius:5px;cursor:pointer;padding:5px 11px 5px 11px;color:#fff;font-weight:bold;background-color:#46a0f5;margin-left:10px}.top-menu .row .reset-btn .i{width:20px;vertical-align:middle;font-size:16px}
|
||||
|
||||
.node{position:absolute;top:0px;left:0px;width:45px;height:45px;transition:all .5s ease;border:3px dashed transparent}.node img{width:45px}.hide-node{display:none}.show-node{display:block}.ok-border{border:3px dashed #46a0f5;border-radius:3px}.noway-border{border:3px dashed #d31d18;border-radius:3px}
|
||||
|
||||
.mask{position:fixed;left:0px;top:0px;right:0px;bottom:0px;background-color:rgba(0,0,0,0.2);z-index:5}.mask .panel{position:absolute;width:600px;top:0px;right:0px;bottom:0px;background-color:#fff;box-shadow:-20px 25px 50px 0px #000;z-index:10;display:flex;flex-direction:column}.hide-mask{display:none}
|
||||
|
||||
.rc-slider-track{background-color:transparet}
|
||||
|
||||
input:focus,textarea:focus,select{outline:none}input.def,textarea,select{height:39px;border:1px solid #c5cbd2;background-color:transparent;width:97%;padding-left:10px;color:#000;font-size:16px;font-family:"Alegreya Sans SC"}textarea{height:80px}select{height:44px}::-webkit-input-placeholder{color:#4f5257}::-ms-input-placeholder{color:red}::-moz-placeholder{color:red}::-moz-placeholder{color:red}
|
||||
|
||||
.react-datepicker-wrapper{width:100%}.react-datepicker__input-container input{height:39px;border:1px solid #c5cbd2;background-color:transparent;width:97%;padding-left:10px;font-family:"Alegreya Sans SC";color:#000;font-size:16px;font-weight:bold}
|
||||
|
||||
.array-container{padding-left:20px;padding-top:20px}.array-container .array-container-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.array-container .array-container-header span{vertical-align:middle;font-size:14px;cursor:pointer}.array-container .array-container-body{height:100px;overflow:auto;border:1px solid #c5cbd2}.array-container .array-container-body .array-container-item{cursor:pointer;line-height:30px;padding-left:10px}.array-container .array-container-body .array-container-selected{background-color:#46a0f5}.array-container .array-container-body .array-container-item:hover{background-color:#e1e3e6}
|
||||
|
||||
.kill-chain-container{padding-left:20px;padding-top:20px}.kill-chain-container .kill-chain-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.kill-chain-container .kill-chain-header span{vertical-align:middle;font-size:14px;cursor:pointer}.kill-chain-container .kill-chain-body{overflow:auto;border:1px solid #c5cbd2}.kill-chain-container .kill-chain-body .kill-chain-options{display:flex;flex-direction:row}.kill-chain-container .kill-chain-body .kill-chain-options select{flex:.5;margin:10px 5px}.kill-chain-container .kill-chain-body .kill-chain-row{padding:5px 5px 5px 10px}.kill-chain-container .kill-chain-body .kill-chain-row .material-icons{vertical-align:middle;color:#d31d18;cursor:pointer}
|
||||
|
||||
.er-container{padding-left:20px;padding-top:20px}.er-container .er-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.er-container .er-header span{vertical-align:middle;font-size:14px;cursor:pointer}.er-container .er-body{overflow:auto;border:1px solid #c5cbd2;min-height:50px}.er-container .er-body .er-block{border:1px solid #c5cbd2;margin:10px}.er-container .er-body .er-block .er-block-row{display:flex;flex-direction:row}.er-container .er-body .er-block .er-block-row select{margin:10px 0px 10px 10px;flex:.5}.er-container .er-body .er-block .er-block-row div{padding:10px;flex:.5}.er-container .er-body .er-block .er-block-row span{vertical-align:middle;cursor:pointer}.er-container .er-body .er-block .er-block-row .remove{color:#d31d18}.er-container .er-body .er-block .er-block-row .add{padding-top:15px;color:#46a0f5}
|
||||
|
||||
|
||||
.boolean{display:flex;flex-direction:row;line-height:30px;border:1px solid #c5cbd2;width:99%}.boolean div{flex:.5;text-align:center;cursor:pointer}.boolean .selected{background-color:#46a0f5}
|
||||
|
||||
.go-container{padding-left:20px;padding-top:20px;padding-right:10px}.go-container .go-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.go-container .go-header span{vertical-align:middle;font-size:14px;cursor:pointer}.go-container .go-body{overflow-x:hidden;border:1px solid #c5cbd2;min-height:50px;width:100%}.go-container .go-body .go-block-input{display:flex;flex-direction:row;padding:5px;width:100%}.go-container .go-body .go-block-input .input{margin:10px 0px 10px 10px;flex:.5}.go-container .go-body .go-block-input .add-container{width:50px;padding:20px 0px 0px 15px}.go-container .go-body .go-block-input .add-container span{cursor:pointer;color:#46a0f5;padding-left:5px}.go-container .go-body .go-block{margin:10px}.go-container .go-body .go-block .go-block-row{display:flex;flex-direction:row;padding:5px}.go-container .go-body .go-block .go-block-row input{margin:10px 0px 10px 10px;flex:.5}.go-container .go-body .go-block .go-block-row div{padding:10px;flex:.5}.go-container .go-body .go-block .go-block-row span{vertical-align:middle;cursor:pointer;padding-left:5px}.go-container .go-body .go-block .go-block-row .remove{color:#d31d18}.go-container .go-body .go-block .go-block-row .add{padding-top:15px;color:#46a0f5}
|
||||
|
||||
.ct-container{padding-left:20px;padding-top:20px;padding-right:10px}.ct-container .ct-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.ct-container .ct-header span{vertical-align:middle;font-size:14px;cursor:pointer}.ct-container .ct-body{overflow-x:hidden;border:1px solid #c5cbd2;min-height:50px;width:100%}.ct-container .ct-body .ct-block-input{display:flex;flex-direction:row;padding:5px;width:100%}.ct-container .ct-body .ct-block-input .input{margin:10px 0px 10px 10px;flex:1}.ct-container .ct-body .ct-block-input .input textarea{font-size:14px;font-family:tahoma}.ct-container .ct-body .ct-block-input .add-container{width:50px;padding:35px 0px 0px 8px}.ct-container .ct-body .ct-block-input .add-container span{cursor:pointer;color:#46a0f5;padding-left:5px}.ct-container .ct-body .ct-output{font-family:tahoma;font-size:14px;padding:0px 15px 15px 15px}.ct-container .ct-body .go-block{margin:10px}.ct-container .ct-body .go-block .go-block-row{display:flex;flex-direction:row;padding:5px}.ct-container .ct-body .go-block .go-block-row input{margin:10px 0px 10px 10px;flex:.5}.ct-container .ct-body .go-block .go-block-row div{padding:10px;flex:.5}.ct-container .ct-body .go-block .go-block-row span{vertical-align:middle;cursor:pointer;padding-left:5px}.ct-container .ct-body .go-block .go-block-row .remove{color:#d31d18}.ct-container .ct-body .go-block .go-block-row .add{padding-top:15px;color:#46a0f5}
|
||||
|
||||
.details{font-size:18px;display:flex;flex-direction:column;height:100%}.details .header{padding:20px;font-size:18px;height:40px;display:flex;flex-direction:row;background-color:#f1f3f5}.details .header .title{padding-top:5px;flex:1}.details .header .title img{vertical-align:middle;padding-right:5px}.details .header .delete{padding-left:7px;width:80px;display:flex;background-color:#46a0f5;border-radius:5px;padding-top:7px;color:#e1e3e6;cursor:pointer;font-size:14px}.details .header .delete span{padding-left:1px}.details .header .delete .text{padding-top:3px}.details .header .delete:hover{background-color:#d31d18}.details .body{overflow-y:scroll;overflow-x:hidden;flex:1}.details .body .item{padding-left:20px;padding-top:15px;font-weight:normal}.details .body .item .horizontal-slider{width:98%}.details .body .item .item-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.details .body .item .item-header span{padding-left:3px;vertical-align:middle;font-size:14px;cursor:pointer}.details .body .slider{padding-bottom:20px}.details .footer{height:40px}
|
||||
|
||||
button:focus{outline:none}button.def{width:auto;min-width:130px;height:30px;color:#e1e3e6;font-family:"Alegreya Sans SC";font-size:14px;border-color:transparent;cursor:pointer}button.def i{vertical-align:middle}button.disabled{background-color:rgba(128,128,128,0.8) !important;color:#c8c5c5 !important;cursor:auto}.disco-relationship{background:transparent;border:1px solid #243244 !important}.standard{background-color:rgba(70,160,245,0.8) !important}.confirm{background-color:rgba(83,129,60,0.8) !important}.caution{background-color:rgba(202,202,57,0.8) !important}.cancel{background-color:rgba(143,44,44,0.8) !important}
|
||||
|
||||
.json-viewer{display:flex;flex-direction:column;flex:1;height:100%;min-height:100px}.json-viewer .json-content{flex:1;overflow-y:auto;overflow-x:hidden;padding:5px 0px 0px 10px;font-family:courier;font-size:13px;display:flex;flex-direction:column}.json-viewer .json-content pre{flex:1}.json-viewer .json-controls{display:flex;flex-direction:row;justify-content:flex-end;height:30px}
|
||||
|
||||
.json-paste{display:flex;flex-direction:column;flex:1;height:100%}.json-paste .paste-area{flex:1;padding:5px 0px 0px 10px;display:flex;flex-direction:column}.json-paste .paste-area div{flex:1}.json-paste .paste-area div textarea{height:97%;font-family:courier;font-size:12px}.json-paste .json-controls{display:flex;flex-direction:row;justify-content:flex-end;height:30px}
|
||||
|
||||
.relationship-picker{display:flex;flex-direction:column;flex:1;height:100%}.relationship-picker .header{padding-top:20px;padding-left:20px;padding-bottom:20px;height:35px}.relationship-picker .header img{vertical-align:middle}.relationship-picker .content{flex:1;overflow:auto}.relationship-picker .content .item{padding-left:20px;padding-top:10px;cursor:pointer;font-weight:bold;font-family:"Alegreya Sans SC";line-height:30px}.relationship-picker .content .item img{vertical-align:middle}.relationship-picker .content .item img.src-image{padding-right:5px}.relationship-picker .content .item img.target-image{padding-right:5px;padding-left:5px}.relationship-picker .content .item .rel-type{color:#46a0f5;padding-left:10px;padding-right:10px}
|
||||
|
||||
.growl{position:fixed;right:10px;top:10px;z-index:15;background-color:#46a0f5;color:#e1e3e6;padding:11px;border-radius:5px}
|
||||
|
||||
.canvas{position:fixed;top:50px;left:50px;bottom:50px;right:50px}
|
||||
|
||||
|
||||
/*# sourceMappingURL=main.css.map*/
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB |
@@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=Alegreya+Sans+SC:bold|Inconsolata|Anton|Permanent+Marker|Roboto:900|Righteous|Ropa+Sans|Titillium+Web:400,700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Two+Tone" rel="stylesheet">
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>STIX 2.1 Modeler</title>
|
||||
<link href="css/vendors.css" rel="stylesheet"><link href="css/main.css" rel="stylesheet"></head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="text/javascript" src="js/runtime~main.1e06bd3d3528e181eea1.js" async></script><script type="text/javascript" src="js/vendors.d617334cf7af0cfc3361.js" async></script><script type="text/javascript" src="js/main.258517e4a0b5fead5d7e.js" async></script></body>
|
||||
</html>
|
||||
@@ -1,2 +0,0 @@
|
||||
!function(e){function r(r){for(var n,l,f=r[0],i=r[1],a=r[2],c=0,s=[];c<f.length;c++)l=f[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,f=1;f<t.length;f++){var i=t[f];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="";var f=window.webpackJsonp=window.webpackJsonp||[],i=f.push.bind(f);f.push=r,f=f.slice();for(var a=0;a<f.length;a++)r(f[a]);var p=i;t()}([]);
|
||||
//# sourceMappingURL=runtime~main.1e06bd3d3528e181eea1.js.map
|
||||
@@ -1,89 +0,0 @@
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
/*!
|
||||
Copyright (c) 2017 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/*! LeaderLine v1.0.5 (c) anseki https://anseki.github.io/leader-line/ */
|
||||
|
||||
/**!
|
||||
* @fileOverview Kickass library to create and place poppers near their reference elements.
|
||||
* @version 1.16.1
|
||||
* @license
|
||||
* Copyright (c) 2016 Federico Zivolo and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/** @license React v16.12.0
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.12.0
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v0.18.0
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.12.0
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -1,20 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Alegreya+Sans+SC:bold|Inconsolata|Anton|Permanent+Marker|Roboto:900|Righteous|Ropa+Sans|Titillium+Web:400,700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Two+Tone" rel="stylesheet">
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>STIX 2.1 Modeler</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
22969
app/package-lock.json
generated
177
app/package.json
@@ -1,150 +1,43 @@
|
||||
{
|
||||
"name": "minimal-webpack-react",
|
||||
"version": "2.0.0",
|
||||
"description": "Boilerplate for react and webpack",
|
||||
"main": "index.js",
|
||||
"name": "stix-modeler-app",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development webpack-dev-server --open",
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"format": "prettier --write 'packages/**/*.js'",
|
||||
"test": "jest --watchAll --coverage"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,scss,css,md}": [
|
||||
"prettier --write --single-quote",
|
||||
"eslint --fix",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"moduleDirectories": [
|
||||
"src",
|
||||
"node_modules"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.js",
|
||||
"\\.(jpg|gif|ttf|eot|svg)$": "<rootDir>/__mocks__/fileMock.js"
|
||||
},
|
||||
"transform": {
|
||||
"^.+\\.(js|jsx)$": "babel-jest",
|
||||
".+\\.(css|styl|less|sass|scss)$": "<rootDir>/node_modules/jest-css-modules-transform"
|
||||
},
|
||||
"setupTestFrameworkScriptFile": "<rootDir>/setupTests.js",
|
||||
"moduleFileExtensions": [
|
||||
"css",
|
||||
"scss",
|
||||
"js",
|
||||
"json",
|
||||
"jsx"
|
||||
]
|
||||
},
|
||||
"repository": "https://github.com/HashemKhalifa/webpack-react-boilerplate",
|
||||
"author": "HashemKhalifa",
|
||||
"license": "ISC",
|
||||
"private": false,
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=3"
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-react-constant-elements": "7.7.4",
|
||||
"@babel/plugin-transform-react-inline-elements": "7.7.4",
|
||||
"axios": "^0.21.1",
|
||||
"babel-plugin-transform-react-pure-class-to-function": "1.0.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||
"core-js": "3.6.1",
|
||||
"deepmerge": "^4.2.2",
|
||||
"lazy-route": "^1.0.7",
|
||||
"leader-line": "^1.0.5",
|
||||
"lodash": "4.17.21",
|
||||
"mobx-react": "^6.1.4",
|
||||
"moment": "^2.24.0",
|
||||
"prop-types": "15.7.2",
|
||||
"rc-slider": "^9.3.0",
|
||||
"react": "16.12.0",
|
||||
"react-datepicker": "^2.16.0",
|
||||
"react-dom": "16.12.0",
|
||||
"@hot-loader/react-dom": "^16.12.0",
|
||||
"react-hot-loader": "4.12.18",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-tooltip": "^4.2.6",
|
||||
"rfx-core": "^1.6.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"babel-core": "7.0.0-bridge.0"
|
||||
"classnames": "^2.5.1",
|
||||
"deepmerge": "^4.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mobx": "^6.12.0",
|
||||
"mobx-react": "^9.1.0",
|
||||
"moment": "^2.30.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"rc-slider": "^10.5.0",
|
||||
"react": "^18.2.0",
|
||||
"react-datepicker": "^6.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-tooltip": "^5.26.3",
|
||||
"reactflow": "^11.10.4",
|
||||
"sass": "^1.71.0",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.7.7",
|
||||
"@babel/plugin-proposal-class-properties": "7.7.4",
|
||||
"@babel/plugin-proposal-decorators": "7.7.4",
|
||||
"@babel/plugin-proposal-export-namespace-from": "7.7.4",
|
||||
"@babel/plugin-proposal-function-sent": "7.7.4",
|
||||
"@babel/plugin-proposal-json-strings": "7.7.4",
|
||||
"@babel/plugin-proposal-numeric-separator": "7.7.4",
|
||||
"@babel/plugin-proposal-throw-expressions": "7.7.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.7.4",
|
||||
"@babel/plugin-syntax-import-meta": "7.7.4",
|
||||
"@babel/plugin-transform-runtime": "7.7.6",
|
||||
"@babel/preset-env": "7.7.7",
|
||||
"@babel/preset-react": "7.7.4",
|
||||
"@babel/register": "7.7.7",
|
||||
"@babel/runtime": "7.7.7",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "10.0.3",
|
||||
"babel-jest": "24.9.0",
|
||||
"babel-loader": "8.0.6",
|
||||
"babel-plugin-lodash": "3.3.4",
|
||||
"babel-preset-react-optimize": "1.0.1",
|
||||
"browserslist": "4.16.5",
|
||||
"clean-webpack-plugin": "3.0.0",
|
||||
"connect-history-api-fallback": "1.6.0",
|
||||
"copy-webpack-plugin": "^6.0.1",
|
||||
"cross-env": "6.0.3",
|
||||
"css-loader": "3.4.0",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-16": "1.15.2",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-config-airbnb": "18.0.1",
|
||||
"eslint-config-prettier": "6.9.0",
|
||||
"eslint-loader": "3.0.3",
|
||||
"eslint-plugin-import": "2.19.1",
|
||||
"eslint-plugin-jest": "23.1.1",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-prettier": "3.1.2",
|
||||
"eslint-plugin-react": "7.17.0",
|
||||
"eslint-watch": "6.0.1",
|
||||
"file-loader": "5.0.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "3.1.0",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"jest": "24.9.0",
|
||||
"jest-css-modules-transform": "3.1.0",
|
||||
"jest-enzyme": "7.1.2",
|
||||
"jest-fetch-mock": "3.0.0",
|
||||
"jsdom": "16.5.0",
|
||||
"koa-connect": "2.0.1",
|
||||
"lint-staged": "9.5.0",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"mobx": "^5.15.1",
|
||||
"node-sass": "^7.0.0",
|
||||
"npm-check-updates": "4.0.1",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||
"prettier": "1.19.1",
|
||||
"pretty-quick": "2.0.1",
|
||||
"sass-loader": "8.0.0",
|
||||
"script-ext-html-webpack-plugin": "2.1.4",
|
||||
"skeleton-loader": "^2.0.0",
|
||||
"style-loader": "1.1.2",
|
||||
"terser-webpack-plugin": "2.3.1",
|
||||
"webpack": "4.41.5",
|
||||
"webpack-cli": "3.3.10",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-merge": "4.2.2"
|
||||
"@types/react": "^18.2.55",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"vite": "^5.2.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"automerge": true,
|
||||
"major": {
|
||||
"automerge": false
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { configure } from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
global.fetch = require('jest-fetch-mock');
|
||||
|
||||
const exposedProperties = ['window', 'navigator', 'document'];
|
||||
const { document } = new JSDOM('').window;
|
||||
global.document = document;
|
||||
global.window = document.defaultView;
|
||||
global.HTMLElement = window.HTMLElement;
|
||||
global.HTMLAnchorElement = window.HTMLAnchorElement;
|
||||
|
||||
Object.keys(document.defaultView).forEach(property => {
|
||||
if (typeof global[property] === 'undefined') {
|
||||
exposedProperties.push(property);
|
||||
global[property] = document.defaultView[property];
|
||||
}
|
||||
});
|
||||
|
||||
global.navigator = {
|
||||
userAgent: 'node.js',
|
||||
};
|
||||
BIN
app/src/.DS_Store
vendored
@@ -1,36 +1,15 @@
|
||||
import React, { Component } from 'react';
|
||||
import {toJS} from 'mobx';
|
||||
import {observer, inject} from 'mobx-react';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import {withRouter, Route, NavLink, useParams} from "react-router-dom";
|
||||
import LazyRoute from "lazy-route";
|
||||
|
||||
import defaultStyle from "./app.scss";
|
||||
|
||||
import { Provider } from 'mobx-react';
|
||||
import Canvas from './components/Canvas';
|
||||
import { store } from './stores/Stores';
|
||||
|
||||
@withRouter
|
||||
@inject("store")
|
||||
@observer
|
||||
class App extends Component {
|
||||
import './App.scss';
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
function App() {
|
||||
return (
|
||||
<div className="content">
|
||||
<Route
|
||||
exact
|
||||
path={'/'}
|
||||
render={(props) =>
|
||||
<Canvas {...props}/>}
|
||||
/>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<Provider store={store}>
|
||||
<Canvas />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default hot(App);
|
||||
export default App;
|
||||
|
||||
@@ -1,505 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { toJS } from "mobx";
|
||||
import PropTypes from "prop-types";
|
||||
import moment from "moment";
|
||||
import Menu from "./menus/Menu";
|
||||
import TopMenu from "./menus/TopMenu";
|
||||
import Node from "./Node";
|
||||
import LeaderLine from "leader-line";
|
||||
import Details from "./Details";
|
||||
import JsonViewer from "./JsonViewer";
|
||||
import JsonPaste from "./JsonPaste";
|
||||
import RelationshipPicker from "./RelationshipPicker";
|
||||
import Growl from "./ui/growl/Growl";
|
||||
import SubmissionError from "./SubmissionError";
|
||||
|
||||
import canvasStyle from "./canvas.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class Canvas extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
document.addEventListener("dragover", (e) => {
|
||||
this.store.setMousePosition(e);
|
||||
}, false);
|
||||
|
||||
this.store = this.props.store.appStore;
|
||||
|
||||
this.generateNodeID = this.generateNodeID.bind(this);
|
||||
|
||||
this.onDragStartHandler = this.onDragStartHandler.bind(this);
|
||||
this.onDragEndHandler = this.onDragEndHandler.bind(this);
|
||||
this.onDragOverHandler = this.onDragOverHandler.bind(this);
|
||||
this.onDropHandler = this.onDropHandler.bind(this);
|
||||
this.onClickHandler = this.onClickHandler.bind(this);
|
||||
this.onDragOverNodeHandler = this.onDragOverNodeHandler.bind(this);
|
||||
this.onDropOnNodeHandler = this.onDropOnNodeHandler.bind(this);
|
||||
this.onClickShowJsonHandler = this.onClickShowJsonHandler.bind(this);
|
||||
this.onClickHideJsonHandler = this.onClickHideJsonHandler.bind(this);
|
||||
this.onClickHideRelPickerHandler = this.onClickHideRelPickerHandler.bind(this);
|
||||
this.onClickHideDetailsHandler = this.onClickHideDetailsHandler.bind(this);
|
||||
this.onClickSelectRelHandler = this.onClickSelectRelHandler.bind(this);
|
||||
this.onClickShowGrowlHandler = this.onClickShowGrowlHandler.bind(this);
|
||||
this.onChangeNodeHandler = this.onChangeNodeHandler.bind(this);
|
||||
this.onChangeDateHandler = this.onChangeDateHandler.bind(this);
|
||||
this.onMessageTimerHandler = this.onMessageTimerHandler.bind(this);
|
||||
this.onClickArrayHandler = this.onClickArrayHandler.bind(this);
|
||||
this.onChangeSliderHandler = this.onChangeSliderHandler.bind(this);
|
||||
this.onChangeCSVHandler = this.onChangeCSVHandler.bind(this);
|
||||
this.onClickBooleanHandler = this.onClickBooleanHandler.bind(this);
|
||||
this.onChangePhaseHandler = this.onChangePhaseHandler.bind(this);
|
||||
this.onClickRemovePhaseHander = this.onClickRemovePhaseHander.bind(this);
|
||||
this.onClickAddObjectHandler = this.onClickAddObjectHandler.bind(this);
|
||||
this.onClickDeletePropertyHandler = this.onClickDeletePropertyHandler.bind(this);
|
||||
this.onClickResetHandler = this.onClickResetHandler.bind(this);
|
||||
this.onChangeERHandler = this.onChangeERHandler.bind(this);
|
||||
this.onChangeGenericObjectHandler = this.onChangeGenericObjectHandler.bind(this);
|
||||
this.onClickAddGenericObjectHandler = this.onClickAddGenericObjectHandler.bind(this);
|
||||
this.onClickDeleteGenericObjectHandler = this.onClickDeleteGenericObjectHandler.bind(this);
|
||||
this.onClickAddTextHandler = this.onClickAddTextHandler.bind(this);
|
||||
this.resetBorders = this.resetBorders.bind(this);
|
||||
this.onClickHideJsonPasteHandler = this.onClickHideJsonPasteHandler.bind(this);
|
||||
this.onClickShowJsonPasteHandler = this.onClickShowJsonPasteHandler.bind(this);
|
||||
this.onChangeJSONPasteHandler = this.onChangeJSONPasteHandler.bind(this);
|
||||
this.onClickJSONPasteHandler = this.onClickJSONPasteHandler.bind(this);
|
||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||
this.onClickSubmitHandler = this.onClickSubmitHandler.bind(this);
|
||||
this.onClickHideSubmissionErrorHandler = this.onClickHideSubmissionErrorHandler.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("dragover", (e) => {
|
||||
}, false);
|
||||
}
|
||||
|
||||
onClickHandler(node) {
|
||||
this.store.showDetails = true;
|
||||
this.store.setSelected(node);
|
||||
}
|
||||
|
||||
onClickHideDetailsHandler() {
|
||||
this.store.showDetails = false;
|
||||
}
|
||||
|
||||
onClickHideJsonPasteHandler() {
|
||||
this.store.showJSONPaste = false;
|
||||
}
|
||||
|
||||
onClickShowJsonPasteHandler() {
|
||||
this.store.showJSONPaste = true;
|
||||
}
|
||||
|
||||
onClickShowGrowlHandler(message) {
|
||||
this.store.growlMessage = message;
|
||||
this.store.showGrowl = true;
|
||||
}
|
||||
|
||||
onClickHideSubmissionErrorHandler() {
|
||||
this.store.resetSubmissionError();
|
||||
}
|
||||
|
||||
transition(id, sticky, random) {
|
||||
const canvas = document.getElementById("canvas");
|
||||
const node = document.getElementById(id);
|
||||
const calculate = (min, max) => {
|
||||
return Math.random() * ((max-100) - min) + min;
|
||||
};
|
||||
const bounds = {
|
||||
top: canvas.offsetTop+50,
|
||||
bottom: (canvas.offsetTop-50) + canvas.clientHeight,
|
||||
left: canvas.offsetLeft+50,
|
||||
right: (canvas.offsetLeft-50) + canvas.clientWidth
|
||||
};
|
||||
|
||||
if (node) {
|
||||
// the first time the node is dropped,
|
||||
// we have to do some class manipulation.
|
||||
if (!sticky) {
|
||||
node.classList.add('show-node');
|
||||
node.classList.remove('hide-node');
|
||||
}
|
||||
|
||||
let clientX = this.store.mousePosition.clientX;
|
||||
let clientY = this.store.mousePosition.clientY;
|
||||
|
||||
if (clientX < bounds.left) {
|
||||
clientX = bounds.left + 50;
|
||||
} else if (clientX > bounds.right) {
|
||||
clientX = bounds.right - 50;
|
||||
}
|
||||
|
||||
if (clientY < bounds.top) {
|
||||
clientY = bounds.top + 50;
|
||||
} else if (clientX > bounds.right) {
|
||||
clientY = bounds.bottom - 50;
|
||||
}
|
||||
|
||||
if (random) {
|
||||
node.style.left = `${calculate(bounds.left, bounds.right)}px`;
|
||||
node.style.top = `${calculate(bounds.top, bounds.bottom)}px`;
|
||||
} else {
|
||||
node.style.left = `${clientX+50}px`;
|
||||
node.style.top = `${clientY-50}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClickDeleteHandler() {
|
||||
this.store.deleteSelectedNode();
|
||||
}
|
||||
|
||||
resetBorders() {
|
||||
this.clearAllCSSBorderCls();
|
||||
this.store.failedRelationship = undefined;
|
||||
}
|
||||
|
||||
onChangeNodeHandler(event) {
|
||||
this.store.editNodeValues(event);
|
||||
}
|
||||
|
||||
mutateOnEvent(property, value) {
|
||||
const event = {
|
||||
currentTarget: {
|
||||
name: property,
|
||||
value: value
|
||||
}
|
||||
}
|
||||
|
||||
this.onChangeNodeHandler(event);
|
||||
}
|
||||
|
||||
onChangeDateHandler(property, datetime) {
|
||||
const value = moment(datetime).format()
|
||||
this.onChangeNodeHandler(property, value);
|
||||
}
|
||||
|
||||
onClickArrayHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onChangeSliderHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onClickBooleanHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onChangePhaseHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onClickAddTextHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onChangeGenericObjectHandler(property, event) {
|
||||
this.mutateOnEvent(property, event.currentTarget.value);
|
||||
}
|
||||
|
||||
onClickRemovePhaseHander(property, value) {
|
||||
this.store.removeKillChainPhase(value);
|
||||
}
|
||||
|
||||
onChangeCSVHandler(event) {
|
||||
this.store.editCSVInput(event);
|
||||
}
|
||||
|
||||
onClickSelectRelHandler(rel) {
|
||||
const persisted = this.store.manuallySelectRelationship(rel);
|
||||
// if the node was persisted, we will want to move it
|
||||
// to a random place on the screen.
|
||||
if (persisted) {
|
||||
this.drawEdge(persisted);
|
||||
|
||||
setTimeout(() => {
|
||||
this.transition(this.store.dragging.id, false);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
onClickAddObjectHandler(field) {
|
||||
this.store.addDefaultObject(field);
|
||||
}
|
||||
|
||||
onClickDeletePropertyHandler(select, idx) {
|
||||
this.store.deleteERObjectProperty(select, idx);
|
||||
}
|
||||
|
||||
onChangeERHandler(input, select, idx) {
|
||||
this.store.changeERValue(input, select, idx);
|
||||
}
|
||||
|
||||
generateNodeID(prefix) {
|
||||
return this.store.generateNodeID(prefix);
|
||||
}
|
||||
|
||||
onClickShowJsonHandler() {
|
||||
this.store.showJSON = true;
|
||||
}
|
||||
|
||||
onClickHideJsonHandler() {
|
||||
this.store.showJSON = false;
|
||||
}
|
||||
|
||||
onChangeJSONPasteHandler(event) {
|
||||
this.store.pasteBundle = event.currentTarget.value;
|
||||
}
|
||||
|
||||
onClickJSONPasteHandler() {
|
||||
this.store.loadBundleFromPaste();
|
||||
|
||||
setTimeout(() => {
|
||||
this.store.nodes.map(n => {
|
||||
this.transition(n.id, false, true);
|
||||
});
|
||||
}, 200);
|
||||
|
||||
this.store.edges.map(e => {
|
||||
this.drawEdge(e);
|
||||
});
|
||||
|
||||
this.store.calculateLineDrag();
|
||||
}
|
||||
|
||||
onClickHideRelPickerHandler() {
|
||||
this.store.showRelPicker = false;
|
||||
}
|
||||
|
||||
onDragStartHandler(event) {
|
||||
let node = JSON.parse(event.dataTransfer.getData("node"));
|
||||
this.store.dragging = node;
|
||||
}
|
||||
|
||||
onDragEndHandler(event) {
|
||||
this.store.calculateLineDrag();
|
||||
}
|
||||
|
||||
// Drag over canvas
|
||||
onDragOverHandler(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
onMessageTimerHandler() {
|
||||
setTimeout(() => {
|
||||
this.store.showGrowl = false;
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
// Drop on canvas
|
||||
onDropHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let node = JSON.parse(event.dataTransfer.getData("node"));
|
||||
|
||||
if (node.properties.type.enum[0] === "observable") {
|
||||
this.store.growlMessage = "Observables can only be dropped onto existing STIX objects.";
|
||||
this.store.showGrowl = true;
|
||||
this.transition(node.id, true);
|
||||
} else {
|
||||
const persisted = this.store.persistNode(node);
|
||||
// if the node was persisted, we will want to move it
|
||||
// to a random place on the screen.
|
||||
if (persisted) {
|
||||
setTimeout(() => {
|
||||
this.transition(node.id, false);
|
||||
}, 200);
|
||||
} else {
|
||||
this.transition(node.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Drag over another node on the screen
|
||||
onDragOverNodeHandler(event, nodeOnScreen) {
|
||||
let canRelate = this.store.canRelate(nodeOnScreen);
|
||||
|
||||
if (event.currentTarget.id != this.store.dragging.id) {
|
||||
if (canRelate) {
|
||||
if (!event.currentTarget.classList.contains("ok-border")) {
|
||||
event.currentTarget.classList.add("ok-border");
|
||||
}
|
||||
} else {
|
||||
if (!event.currentTarget.classList.contains("noway-border")) {
|
||||
event.currentTarget.classList.add("noway-border");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
clearAllCSSBorderCls() {
|
||||
var nodes = document.getElementsByClassName("node");
|
||||
|
||||
for (let i=0; i<nodes.length; i++) {
|
||||
if (
|
||||
nodes[i].classList.contains("ok-border") ||
|
||||
nodes[i].classList.contains("noway-border")
|
||||
) {
|
||||
nodes[i].classList.remove("noway-border");
|
||||
nodes[i].classList.remove("ok-border");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Drop on another node on the screen
|
||||
onDropOnNodeHandler(nodeOnScreen) {
|
||||
const persisted = this.store.addNodeWithRelationship(nodeOnScreen);
|
||||
|
||||
if (persisted) {
|
||||
this.drawEdge(persisted);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.transition(this.store.dragging.id, false);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
this.clearAllCSSBorderCls();
|
||||
}
|
||||
|
||||
onClickAddGenericObjectHandler(field, o) {
|
||||
this.store.addGenericObject(field, o);
|
||||
}
|
||||
|
||||
onClickDeleteGenericObjectHandler(field, key) {
|
||||
this.store.deleteGenericObject(field, key);
|
||||
}
|
||||
|
||||
onClickResetHandler() {
|
||||
this.store.reset();
|
||||
}
|
||||
|
||||
onClickSubmitHandler() {
|
||||
this.store.submit();
|
||||
}
|
||||
|
||||
drawEdge(persisted) {
|
||||
let s = persisted.source_ref;
|
||||
let t = persisted.target_ref;
|
||||
|
||||
setTimeout(() => {
|
||||
this.transition(this.store.dragging.id, false);
|
||||
|
||||
let line = new LeaderLine(
|
||||
document.getElementById(s),
|
||||
document.getElementById(t)
|
||||
)
|
||||
|
||||
let labelProps = LeaderLine.pathLabel(
|
||||
persisted.relationship_type, {
|
||||
color: '#484d59',
|
||||
outlineColor: '#484d59',
|
||||
fontWeight: '1px',
|
||||
letterSpacing: '2px',
|
||||
lineOffset: '13px'
|
||||
}
|
||||
);
|
||||
|
||||
line.setOptions({
|
||||
startSocket: 'disc',
|
||||
endSocket: 'disc',
|
||||
middleLabel: labelProps,
|
||||
dash: {
|
||||
animation: false
|
||||
}
|
||||
});
|
||||
|
||||
this.store.lines.push(line);
|
||||
|
||||
}, 200);
|
||||
}
|
||||
|
||||
render() {
|
||||
const nodes = this.store.nodes;
|
||||
const elements = Array.from(document.getElementsByTagName("svg"));
|
||||
|
||||
// This ensures the edges drawn will not
|
||||
// interfere with the slide out panels.
|
||||
elements.map(element => {
|
||||
element.style.zIndex = "-1";
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="canvas"
|
||||
className="canvas"
|
||||
onDragOver={this.onDragOverHandler}
|
||||
onDrop={this.onDropHandler}>
|
||||
|
||||
{
|
||||
nodes.map(n => {
|
||||
return <Node key={n.id}
|
||||
n={n}
|
||||
failedRelationship={this.store.failedRelationship}
|
||||
resetBorders={this.resetBorders}
|
||||
onClickHandler={this.onClickHandler}
|
||||
onDragStartHandler={this.onDragStartHandler}
|
||||
onDragEndHandler={this.onDragEndHandler}
|
||||
onDragOverNodeHandler={this.onDragOverNodeHandler}
|
||||
onDropOnNodeHandler={this.onDropOnNodeHandler} />
|
||||
})
|
||||
}
|
||||
|
||||
<TopMenu onClickShowJsonHandler={this.onClickShowJsonHandler}
|
||||
onClickShowJsonPasteHandler={this.onClickShowJsonPasteHandler}
|
||||
onClickHideJsonHandler={this.onClickHideJsonHandler}
|
||||
onClickResetHandler={this.onClickResetHandler}
|
||||
onClickSubmitHandler={this.onClickSubmitHandler} />
|
||||
|
||||
<Menu
|
||||
objects={this.store.objects}
|
||||
onDragStartHandler={this.onDragStartHandler}
|
||||
generateNodeID={this.generateNodeID} />
|
||||
|
||||
<Details show={this.store.showDetails}
|
||||
node={this.store.selected}
|
||||
onClickHideHandler={this.onClickHideDetailsHandler}
|
||||
onChangeNodeHandler={this.onChangeNodeHandler}
|
||||
onChangeDateHandler={this.onChangeDateHandler}
|
||||
onClickArrayHandler={this.onClickArrayHandler}
|
||||
onChangeSliderHandler={this.onChangeSliderHandler}
|
||||
onChangeCSVHandler={this.onChangeCSVHandler}
|
||||
onClickBooleanHandler={this.onClickBooleanHandler}
|
||||
onChangePhaseHandler={this.onChangePhaseHandler}
|
||||
onClickRemovePhaseHander={this.onClickRemovePhaseHander}
|
||||
onClickAddObjectHandler={this.onClickAddObjectHandler}
|
||||
onChangeERHandler={this.onChangeERHandler}
|
||||
onClickDeletePropertyHandler={this.onClickDeletePropertyHandler}
|
||||
onChangeGenericObjectHandler={this.onChangeGenericObjectHandler}
|
||||
onClickAddGenericObjectHandler={this.onClickAddGenericObjectHandler}
|
||||
onClickDeleteGenericObjectHandler={this.onClickDeleteGenericObjectHandler}
|
||||
onClickAddTextHandler={this.onClickAddTextHandler}
|
||||
onClickDeleteHandler={this.onClickDeleteHandler} />
|
||||
|
||||
<JsonViewer show={this.store.showJSON}
|
||||
json={this.store.bundle}
|
||||
onClickHideHandler={this.onClickHideJsonHandler}
|
||||
onClickShowGrowlHandler={this.onClickShowGrowlHandler} />
|
||||
|
||||
<JsonPaste show={this.store.showJSONPaste}
|
||||
json={this.store.pasteBundle}
|
||||
onClickHideHandler={this.onClickHideJsonPasteHandler}
|
||||
onChangeJSONPasteHandler={this.onChangeJSONPasteHandler}
|
||||
onClickJSONPasteHandler={this.onClickJSONPasteHandler}
|
||||
value={this.store.pasteBundle} />
|
||||
|
||||
<RelationshipPicker show={this.store.showRelPicker}
|
||||
relationships={this.store.relationships}
|
||||
onClickHideHandler={this.onClickHideRelPickerHandler}
|
||||
onClickSelectRelHandler={this.onClickSelectRelHandler} />
|
||||
|
||||
<Growl message={this.store.growlMessage}
|
||||
show={this.store.showGrowl}
|
||||
timer={this.onMessageTimerHandler} />
|
||||
|
||||
<SubmissionError error={this.store.failedCollection}
|
||||
show={this.store.showSubmissionError}
|
||||
onClickHideHandler={this.onClickHideSubmissionErrorHandler} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
736
app/src/components/Canvas.jsx
Normal file
@@ -0,0 +1,736 @@
|
||||
import React from 'react';
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import BottomMenu from './menus/BottomMenu';
|
||||
import TopMenu from './menus/TopMenu';
|
||||
import Details from './Details';
|
||||
import SDOEditor from './schema/SDOEditor';
|
||||
import FileImporter from './FileImporter';
|
||||
import JsonViewer from './bundle/JsonViewer';
|
||||
import JsonPaste from './bundle/JsonPaste';
|
||||
import SchemaPaste from './schema/SchemaPaste';
|
||||
import RelationshipPicker from './relationship/RelationshipPicker';
|
||||
import RelationshipDetails from './relationship/RelationshipDetails';
|
||||
import RelationshipEditor from './relationship/RelationshipEditor';
|
||||
import SDOPicker from './schema/SDOPicker';
|
||||
import Growl from './ui/growl/Growl';
|
||||
import SubmissionError from './SubmissionError';
|
||||
import Flow from './Flow/Flow';
|
||||
|
||||
import './canvas.scss';
|
||||
|
||||
class Canvas extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.store = this.props.store.appStore;
|
||||
|
||||
this.generateNodeID = this.generateNodeID.bind(this);
|
||||
this.setUpdateFlow = this.setUpdateFlow.bind(this);
|
||||
this.onDragStartHandler = this.onDragStartHandler.bind(this);
|
||||
this.onDragOverHandler = this.onDragOverHandler.bind(this);
|
||||
this.onDropHandler = this.onDropHandler.bind(this);
|
||||
this.onClickHandler = this.onClickHandler.bind(this);
|
||||
this.onClickRelHandler = this.onClickRelHandler.bind(this);
|
||||
this.setMousePosition = this.setMousePosition.bind(this);
|
||||
this.onConnectNodeHandler = this.onConnectNodeHandler.bind(this);
|
||||
this.onDragStopNodeHandler = this.onDragStopNodeHandler.bind(this);
|
||||
this.onClickShowJsonHandler = this.onClickShowJsonHandler.bind(this);
|
||||
this.onClickHideJsonHandler = this.onClickHideJsonHandler.bind(this);
|
||||
this.onClickHideRelPickerHandler = this.onClickHideRelPickerHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickHideRelDetailsHandler = this.onClickHideRelDetailsHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickHideRelEditorHandler = this.onClickHideRelEditorHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickShowRelDetailsHandler = this.onClickShowRelDetailsHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickShowSDOPickerHandler = this.onClickShowSDOPickerHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickHideSDOPickerHandler = this.onClickHideSDOPickerHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickShowImporterHandler = this.onClickShowImporterHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickHideImporterHandler = this.onClickHideImporterHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickHideDetailsHandler = this.onClickHideDetailsHandler.bind(this);
|
||||
this.onClickHideEditorHandler = this.onClickHideEditorHandler.bind(this);
|
||||
this.onClickCreateRelHandler = this.onClickCreateRelHandler.bind(this);
|
||||
this.onClickEditRelHandler = this.onClickEditRelHandler.bind(this);
|
||||
this.onClickSelectRelHandler = this.onClickSelectRelHandler.bind(this);
|
||||
this.onClickSelectSDOHandler = this.onClickSelectSDOHandler.bind(this);
|
||||
this.onClickShowGrowlHandler = this.onClickShowGrowlHandler.bind(this);
|
||||
this.onClickGroupNodeHandler = this.onClickGroupNodeHandler.bind(this);
|
||||
this.onClickGroupModeHandler = this.onClickGroupModeHandler.bind(this);
|
||||
this.onClickSubmitGroupingHandler = this.onClickSubmitGroupingHandler.bind(this);
|
||||
this.onChangeNodeHandler = this.onChangeNodeHandler.bind(this);
|
||||
this.onChangeSDOHandler = this.onChangeSDOHandler.bind(this);
|
||||
this.onChangeSchemaHandler = this.onChangeSchemaHandler.bind(this);
|
||||
this.onChangeBundleHandler = this.onChangeBundleHandler.bind(this);
|
||||
this.onChangeDateHandler = this.onChangeDateHandler.bind(this);
|
||||
this.onMessageTimerHandler = this.onMessageTimerHandler.bind(this);
|
||||
this.onClickArrayHandler = this.onClickArrayHandler.bind(this);
|
||||
this.onChangeListHandler = this.onChangeListHandler.bind(this);
|
||||
this.onChangeSliderHandler = this.onChangeSliderHandler.bind(this);
|
||||
this.onChangeCSVHandler = this.onChangeCSVHandler.bind(this);
|
||||
this.onChangeCreatorIDHandler = this.onChangeCreatorIDHandler.bind(this);
|
||||
this.onClickBooleanHandler = this.onClickBooleanHandler.bind(this);
|
||||
this.onChangePhaseHandler = this.onChangePhaseHandler.bind(this);
|
||||
this.onClickRemovePhaseHander = this.onClickRemovePhaseHander.bind(this);
|
||||
this.onClickAddObjectHandler = this.onClickAddObjectHandler.bind(this);
|
||||
this.onClickDeletePropertyHandler = this.onClickDeletePropertyHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickResetHandler = this.onClickResetHandler.bind(this);
|
||||
this.onChangeERHandler = this.onChangeERHandler.bind(this);
|
||||
this.onClickDeleteERHandler = this.onClickDeleteERHandler.bind(this);
|
||||
this.onChangeArrayObjectHandler = this.onChangeArrayObjectHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickDeleteArrayObjectHandler = this.onClickDeleteArrayObjectHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickDeleteArrayObjectPropertyHandler = this.onClickDeleteArrayObjectPropertyHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onChangeGenericObjectHandler = this.onChangeGenericObjectHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickAddGenericObjectHandler = this.onClickAddGenericObjectHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickDeleteGenericObjectHandler = this.onClickDeleteGenericObjectHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickAddTextHandler = this.onClickAddTextHandler.bind(this);
|
||||
this.onClickHideJsonPasteHandler = this.onClickHideJsonPasteHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickShowJsonPasteHandler = this.onClickShowJsonPasteHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickHideSchemaPasteHandler = this.onClickHideSchemaPasteHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickShowSchemaPasteHandler = this.onClickShowSchemaPasteHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onChangeJSONPasteHandler = this.onChangeJSONPasteHandler.bind(this);
|
||||
this.onClickJSONPasteHandler = this.onClickJSONPasteHandler.bind(this);
|
||||
this.onChangeSchemaPasteHandler = this.onChangeSchemaPasteHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickSchemaPasteHandler = this.onClickSchemaPasteHandler.bind(this);
|
||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||
this.onClickDeleteSDOHandler = this.onClickDeleteSDOHandler.bind(this);
|
||||
this.onClickDeleteRelHandler = this.onClickDeleteRelHandler.bind(this);
|
||||
this.onClickSubmitHandler = this.onClickSubmitHandler.bind(this);
|
||||
this.onClickHideSubmissionErrorHandler = this.onClickHideSubmissionErrorHandler.bind(
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('dragover', () => {}, false);
|
||||
}
|
||||
|
||||
onClickHandler(nodeId) {
|
||||
const node = this.store.getNodeById(nodeId);
|
||||
this.store.setShowDetails(true);
|
||||
this.store.setSelected(node);
|
||||
}
|
||||
|
||||
onClickGroupModeHandler(isGrouping) {
|
||||
this.store.setGroupMode(isGrouping);
|
||||
if (!isGrouping) {
|
||||
this.store.resetGroup();
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
}
|
||||
|
||||
onClickGroupNodeHandler(id) {
|
||||
this.store.modifyGroup(id);
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onClickSubmitGroupingHandler() {
|
||||
const id = this.generateNodeID('grouping--');
|
||||
this.store.createGroup(id);
|
||||
this.transition(id, true);
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onClickRelHandler(relId) {
|
||||
const rel = this.store.getRelById(relId);
|
||||
this.store.setShowRelEditor(true);
|
||||
this.store.setSelectedRel(rel);
|
||||
}
|
||||
|
||||
onClickHideDetailsHandler() {
|
||||
this.store.setShowDetails(false);
|
||||
}
|
||||
|
||||
onClickHideEditorHandler() {
|
||||
this.store.setShowEditor(false);
|
||||
}
|
||||
|
||||
onClickHideJsonPasteHandler() {
|
||||
this.store.setShowJSONPaste(false);
|
||||
}
|
||||
|
||||
onClickShowJsonPasteHandler() {
|
||||
this.store.setShowJSONPaste(true);
|
||||
}
|
||||
|
||||
onClickHideSchemaPasteHandler() {
|
||||
this.store.setShowSchemaPaste(false);
|
||||
}
|
||||
|
||||
onClickShowSchemaPasteHandler() {
|
||||
this.store.setShowSchemaPaste(true);
|
||||
}
|
||||
|
||||
onClickShowGrowlHandler(message) {
|
||||
this.store.setGrowlMessage(message);
|
||||
this.store.setShowGrowl(true);
|
||||
}
|
||||
|
||||
onClickHideSubmissionErrorHandler() {
|
||||
this.store.resetSubmissionError();
|
||||
}
|
||||
|
||||
onClickDeleteHandler() {
|
||||
this.store.deleteSelectedNode();
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onClickDeleteSDOHandler() {
|
||||
this.store.deleteSelectedSDO();
|
||||
}
|
||||
|
||||
onClickDeleteRelHandler() {
|
||||
this.store.deleteSelectedRelationship();
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onChangeNodeHandler(event) {
|
||||
this.store.editNodeValues(event);
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onChangeSDOHandler(event) {
|
||||
this.store.editSDOValues(event);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
onChangeSchemaHandler(file) {
|
||||
this.store.loadSchemaFromFile(file);
|
||||
}
|
||||
|
||||
onChangeBundleHandler(file) {
|
||||
this.store.loadBundleFromFile(file);
|
||||
this.store.nodes.map((n) => {
|
||||
this.transition(n.id, true);
|
||||
});
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onChangeCreatorIDHandler(id) {
|
||||
this.store.updateCreatorID(id);
|
||||
}
|
||||
|
||||
onChangeDateHandler(property, datetime) {
|
||||
const value = this.store.generateTimestamp(datetime);
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onClickArrayHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onChangeSliderHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onClickBooleanHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onChangePhaseHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onClickAddTextHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onChangeListHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
onChangeGenericObjectHandler(property, event) {
|
||||
this.mutateOnEvent(property, event.currentTarget.value);
|
||||
}
|
||||
|
||||
onClickRemovePhaseHander(property, value) {
|
||||
this.store.removeKillChainPhase(value);
|
||||
}
|
||||
|
||||
onChangeCSVHandler(event) {
|
||||
this.store.editCSVInput(event);
|
||||
}
|
||||
|
||||
onClickCreateRelHandler(srcId, targetId, rel) {
|
||||
const src = { id: srcId, };
|
||||
const target = { id: targetId, };
|
||||
const relationship = this.store.makeRelationship(src, target, rel);
|
||||
if (relationship) {
|
||||
this.onClickSelectRelHandler(relationship);
|
||||
this.store.addCustomRelationship(rel, srcId, targetId);
|
||||
this.setUpdateFlow(true);
|
||||
} else {
|
||||
this.store.setGrowlMessage('Could not create relationship');
|
||||
this.store.setShowGrowl(true);
|
||||
}
|
||||
}
|
||||
|
||||
onClickEditRelHandler(rel) {
|
||||
this.store.editRelationship(rel);
|
||||
this.store.setShowRelEditor(false);
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onClickSelectRelHandler(relationship) {
|
||||
this.store.setShowRelDetails(false);
|
||||
this.store.manuallySelectRelationship(relationship);
|
||||
this.transition(this.store.dragging.id, true);
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onClickSelectSDOHandler(sdo) {
|
||||
this.store.setSelectedSDO(sdo);
|
||||
this.store.setShowEditor(true);
|
||||
}
|
||||
|
||||
onClickAddObjectHandler(field, requiredFields) {
|
||||
this.store.addDefaultObject(field, requiredFields);
|
||||
}
|
||||
|
||||
onClickDeletePropertyHandler(select, idx) {
|
||||
this.store.deleteERObjectProperty(select, idx);
|
||||
}
|
||||
|
||||
onClickDeleteArrayObjectPropertyHandler(select, idx, property) {
|
||||
this.store.deleteArrayObjectProperty(select, idx, property);
|
||||
}
|
||||
|
||||
onClickDeleteERHandler(idx) {
|
||||
this.store.deleteERObject(idx);
|
||||
}
|
||||
|
||||
onClickDeleteArrayObjectHandler(idx, property) {
|
||||
this.store.deleteArrayObject(idx, property);
|
||||
}
|
||||
|
||||
onChangeERHandler(input, select, idx) {
|
||||
this.store.changeERValue(input, select, idx);
|
||||
}
|
||||
|
||||
onChangeArrayObjectHandler(input, field, idx, property) {
|
||||
this.store.changeArrayObjectValue(input, field, idx, property);
|
||||
}
|
||||
|
||||
onClickShowJsonHandler() {
|
||||
this.store.mutateBundle();
|
||||
this.store.setShowJSON(true);
|
||||
}
|
||||
|
||||
onClickHideJsonHandler() {
|
||||
this.store.setShowJSON(false);
|
||||
}
|
||||
|
||||
onChangeJSONPasteHandler(event) {
|
||||
this.store.setPasteBundle(event.currentTarget.value);
|
||||
}
|
||||
|
||||
onClickJSONPasteHandler() {
|
||||
this.store.loadBundleFromPaste();
|
||||
|
||||
this.store.nodes.map((n) => {
|
||||
this.transition(n.id, true);
|
||||
});
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onChangeSchemaPasteHandler(event) {
|
||||
this.store.setPasteSchema(event.currentTarget.value);
|
||||
}
|
||||
|
||||
onClickSchemaPasteHandler() {
|
||||
this.store.loadSchemaFromPaste();
|
||||
}
|
||||
|
||||
onClickShowRelDetailsHandler() {
|
||||
this.store.setShowRelDetails(true);
|
||||
this.store.setShowRelPicker(false);
|
||||
}
|
||||
|
||||
onClickHideRelDetailsHandler() {
|
||||
this.store.setShowRelDetails(false);
|
||||
this.store.setShowRelPicker(true);
|
||||
}
|
||||
|
||||
onClickHideRelEditorHandler() {
|
||||
this.store.setShowRelEditor(false);
|
||||
}
|
||||
|
||||
onClickHideRelPickerHandler() {
|
||||
this.store.setShowRelPicker(false);
|
||||
}
|
||||
|
||||
onClickShowSDOPickerHandler() {
|
||||
this.store.setShowSDOPicker(true);
|
||||
}
|
||||
|
||||
onClickHideSDOPickerHandler() {
|
||||
this.store.setShowSDOPicker(false);
|
||||
}
|
||||
|
||||
onClickHideImporterHandler() {
|
||||
this.store.setShowImporter(false);
|
||||
}
|
||||
|
||||
onClickShowImporterHandler() {
|
||||
this.store.setShowImporter(true);
|
||||
}
|
||||
|
||||
onDragOverHandler(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
onDragStartHandler(event) {
|
||||
const node = JSON.parse(event.dataTransfer.getData('node'));
|
||||
this.store.setDragging(node);
|
||||
}
|
||||
|
||||
onMessageTimerHandler() {
|
||||
setTimeout(() => {
|
||||
this.store.setShowGrowl(false);
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
// Drop on canvas
|
||||
onDropHandler(event) {
|
||||
event.preventDefault();
|
||||
const node = this.store.dragging;
|
||||
if (node.properties.type.enum[0] === 'observable') {
|
||||
const source = this.store.getNodeByPosition(
|
||||
this.store.mousePosition.clientX,
|
||||
this.store.mousePosition.clientY
|
||||
);
|
||||
if (source) {
|
||||
const genericTarget = {
|
||||
id: '',
|
||||
relationships: [],
|
||||
properties: { type: { enum: ['observable'], }, },
|
||||
};
|
||||
|
||||
const canRelate = this.store.canRelate(source, genericTarget);
|
||||
if (canRelate.length > 1) {
|
||||
this.store.setRelationships(canRelate);
|
||||
this.store.setShowRelPicker(true);
|
||||
} else if (canRelate.length === 1) {
|
||||
this.store.manuallySelectRelationship(canRelate[0]);
|
||||
this.transition(this.store.dragging.id, true);
|
||||
this.setUpdateFlow(true);
|
||||
} else {
|
||||
const sourceType = source.properties.type.enum[0];
|
||||
this.store.setShowGrowl(true);
|
||||
this.store.setGrowlMessage(`${sourceType} has no possible observables.`);
|
||||
}
|
||||
} else {
|
||||
this.store.setGrowlMessage('Observables can only be dropped onto existing STIX objects.');
|
||||
this.store.setShowGrowl(true);
|
||||
}
|
||||
} else {
|
||||
this.store.addCreatorID(node);
|
||||
const persisted = this.store.persistNode(node);
|
||||
// if the node was persisted, we will want to set
|
||||
// its position on the screen
|
||||
if (persisted) {
|
||||
this.transition(node.id);
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect two nodes via a new relationship
|
||||
onConnectNodeHandler(sourceId, targetId) {
|
||||
const sourceNode = this.store.getNodeById(sourceId);
|
||||
const targetNode = this.store.getNodeById(targetId);
|
||||
const canRelate = this.store.canRelate(sourceNode, targetNode);
|
||||
|
||||
if (targetNode.id !== sourceNode.id) {
|
||||
const genericRel = {
|
||||
source_ref: sourceId,
|
||||
target_ref: targetId,
|
||||
target: targetNode.properties.type.enum[0],
|
||||
};
|
||||
this.store.setRelationships(canRelate);
|
||||
this.store.relationships.unshift(genericRel);
|
||||
this.store.setShowRelPicker(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Update store position from React Flow
|
||||
onDragStopNodeHandler(node) {
|
||||
const n = this.store.getNodeById(node.id);
|
||||
if (n) {
|
||||
n.position = node.position;
|
||||
}
|
||||
}
|
||||
|
||||
onClickAddGenericObjectHandler(field, o) {
|
||||
this.store.addGenericObject(field, o);
|
||||
}
|
||||
|
||||
onClickDeleteGenericObjectHandler(field, key) {
|
||||
this.store.deleteGenericObject(field, key);
|
||||
}
|
||||
|
||||
onClickResetHandler() {
|
||||
this.store.reset();
|
||||
}
|
||||
|
||||
onClickSubmitHandler() {
|
||||
this.store.submit();
|
||||
}
|
||||
|
||||
setUpdateFlow(update) {
|
||||
this.store.setUpdateFlow(update);
|
||||
}
|
||||
|
||||
setMousePosition(x, y) {
|
||||
this.store.setMousePosition(x, y);
|
||||
}
|
||||
|
||||
generateNodeID(prefix) {
|
||||
return this.store.generateNodeID(prefix);
|
||||
}
|
||||
|
||||
mutateOnEvent(property, value) {
|
||||
const event = {
|
||||
currentTarget: {
|
||||
name: property,
|
||||
value,
|
||||
},
|
||||
};
|
||||
|
||||
this.onChangeNodeHandler(event);
|
||||
}
|
||||
|
||||
transition(id, random) {
|
||||
const canvas = document.getElementById('canvas');
|
||||
const node = this.store.getNodeById(id);
|
||||
|
||||
const calculate = (min, max) => Math.random() * (max - 100 - min) + min;
|
||||
|
||||
const bounds = {
|
||||
top: canvas.offsetTop + 25,
|
||||
bottom: canvas.offsetTop - 25 + canvas.clientHeight,
|
||||
left: canvas.offsetLeft + 25,
|
||||
right: canvas.offsetLeft - 25 + canvas.clientWidth,
|
||||
};
|
||||
|
||||
if (node) {
|
||||
// Initialize position
|
||||
node.position = { x: 0, y: 0, };
|
||||
|
||||
const { clientX, } = this.store.mousePosition;
|
||||
const { clientY, } = this.store.mousePosition;
|
||||
|
||||
if (random) {
|
||||
node.position.x = calculate(bounds.left, bounds.right);
|
||||
node.position.y = calculate(bounds.top, bounds.bottom);
|
||||
} else {
|
||||
node.position.x = clientX - 50;
|
||||
node.position.y = clientY - 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { nodes, } = this.store;
|
||||
const { edges, } = this.store;
|
||||
const sdos = this.store.getCustomSDOs();
|
||||
|
||||
return (
|
||||
<div
|
||||
id="canvas"
|
||||
className="canvas"
|
||||
onDragOver={this.onDragOverHandler}
|
||||
onDrop={this.onDropHandler}
|
||||
>
|
||||
<Flow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
groupMode={this.store.groupMode}
|
||||
updateFlow={this.store.updateFlow}
|
||||
setUpdateFlow={this.setUpdateFlow}
|
||||
onClickHandler={this.onClickHandler}
|
||||
onClickGroupNodeHandler={this.onClickGroupNodeHandler}
|
||||
onClickRelHandler={this.onClickRelHandler}
|
||||
onDragStopNodeHandler={this.onDragStopNodeHandler}
|
||||
setMousePosition={this.setMousePosition}
|
||||
onConnectNodeHandler={this.onConnectNodeHandler}
|
||||
/>
|
||||
|
||||
<TopMenu
|
||||
onClickShowJsonHandler={this.onClickShowJsonHandler}
|
||||
onClickShowJsonPasteHandler={this.onClickShowJsonPasteHandler}
|
||||
onClickShowSchemaPasteHandler={this.onClickShowSchemaPasteHandler}
|
||||
onClickHideJsonHandler={this.onClickHideJsonHandler}
|
||||
onClickResetHandler={this.onClickResetHandler}
|
||||
onClickSubmitHandler={this.onClickSubmitHandler}
|
||||
onClickShowSDOPickerHandler={this.onClickShowSDOPickerHandler}
|
||||
onClickShowImporterHandler={this.onClickShowImporterHandler}
|
||||
onChangeCreatorIDHandler={this.onChangeCreatorIDHandler}
|
||||
onClickGroupModeHandler={this.onClickGroupModeHandler}
|
||||
onClickSubmitGroupingHandler={this.onClickSubmitGroupingHandler}
|
||||
creatorID={this.store.creatorID}
|
||||
groupMode={this.store.groupMode}
|
||||
/>
|
||||
|
||||
<BottomMenu
|
||||
objects={this.store.objects}
|
||||
imgs={this.store.objects.map((o) => o.customImg)}
|
||||
onDragStartHandler={this.onDragStartHandler}
|
||||
generateNodeID={this.generateNodeID}
|
||||
/>
|
||||
|
||||
<Details
|
||||
show={this.store.showDetails}
|
||||
node={this.store.selected}
|
||||
onClickHideHandler={this.onClickHideDetailsHandler}
|
||||
onChangeNodeHandler={this.onChangeNodeHandler}
|
||||
onChangeDateHandler={this.onChangeDateHandler}
|
||||
onClickArrayHandler={this.onClickArrayHandler}
|
||||
onChangeListHandler={this.onChangeListHandler}
|
||||
onChangeSliderHandler={this.onChangeSliderHandler}
|
||||
onChangeCSVHandler={this.onChangeCSVHandler}
|
||||
onClickBooleanHandler={this.onClickBooleanHandler}
|
||||
onChangePhaseHandler={this.onChangePhaseHandler}
|
||||
onClickRemovePhaseHander={this.onClickRemovePhaseHander}
|
||||
onClickAddObjectHandler={this.onClickAddObjectHandler}
|
||||
onClickDeleteERHandler={this.onClickDeleteERHandler}
|
||||
onChangeERHandler={this.onChangeERHandler}
|
||||
onClickDeletePropertyHandler={this.onClickDeletePropertyHandler}
|
||||
onClickDeleteArrayObjectHandler={this.onClickDeleteArrayObjectHandler}
|
||||
onChangeArrayObjectHandler={this.onChangeArrayObjectHandler}
|
||||
onClickDeleteArrayObjectPropertyHandler={
|
||||
this.onClickDeleteArrayObjectPropertyHandler
|
||||
}
|
||||
onChangeGenericObjectHandler={this.onChangeGenericObjectHandler}
|
||||
onClickAddGenericObjectHandler={this.onClickAddGenericObjectHandler}
|
||||
onClickDeleteGenericObjectHandler={
|
||||
this.onClickDeleteGenericObjectHandler
|
||||
}
|
||||
onClickAddTextHandler={this.onClickAddTextHandler}
|
||||
onClickDeleteHandler={this.onClickDeleteHandler}
|
||||
/>
|
||||
|
||||
<RelationshipDetails
|
||||
show={this.store.showRelDetails}
|
||||
relationships={this.store.relationships}
|
||||
node={this.store.selected}
|
||||
onClickHideHandler={this.onClickHideRelDetailsHandler}
|
||||
onClickCreateRelHandler={this.onClickCreateRelHandler}
|
||||
/>
|
||||
|
||||
<RelationshipEditor
|
||||
show={this.store.showRelEditor}
|
||||
relationship={this.store.selectedRel}
|
||||
key={this.store.selectedRel.relationship_type}
|
||||
onClickHideHandler={this.onClickHideRelEditorHandler}
|
||||
onClickEditRelHandler={this.onClickEditRelHandler}
|
||||
onClickDeleteRelHandler={this.onClickDeleteRelHandler}
|
||||
/>
|
||||
|
||||
<SDOEditor
|
||||
show={this.store.showEditor}
|
||||
sdo={this.store.selectedSDO}
|
||||
onClickHideHandler={this.onClickHideEditorHandler}
|
||||
onChangeSDOHandler={this.onChangeSDOHandler}
|
||||
onClickDeleteHandler={this.onClickDeleteSDOHandler}
|
||||
/>
|
||||
|
||||
<FileImporter
|
||||
show={this.store.showImporter}
|
||||
onClickHideHandler={this.onClickHideImporterHandler}
|
||||
onChangeSchemaHandler={this.onChangeSchemaHandler}
|
||||
onChangeBundleHandler={this.onChangeBundleHandler}
|
||||
/>
|
||||
|
||||
<JsonViewer
|
||||
show={this.store.showJSON}
|
||||
json={this.store.mutatedBundle}
|
||||
onClickHideHandler={this.onClickHideJsonHandler}
|
||||
onClickShowGrowlHandler={this.onClickShowGrowlHandler}
|
||||
/>
|
||||
|
||||
<JsonPaste
|
||||
show={this.store.showJSONPaste}
|
||||
json={this.store.pasteBundle}
|
||||
onClickHideHandler={this.onClickHideJsonPasteHandler}
|
||||
onChangeJSONPasteHandler={this.onChangeJSONPasteHandler}
|
||||
onClickJSONPasteHandler={this.onClickJSONPasteHandler}
|
||||
value={this.store.pasteBundle}
|
||||
/>
|
||||
|
||||
<SchemaPaste
|
||||
show={this.store.showSchemaPaste}
|
||||
json={this.store.pasteSchema}
|
||||
onClickHideHandler={this.onClickHideSchemaPasteHandler}
|
||||
onChangeSchemaPasteHandler={this.onChangeSchemaPasteHandler}
|
||||
onClickSchemaPasteHandler={this.onClickSchemaPasteHandler}
|
||||
value={this.store.pasteSchema}
|
||||
/>
|
||||
|
||||
<RelationshipPicker
|
||||
show={this.store.showRelPicker}
|
||||
relationships={this.store.relationships}
|
||||
onClickHideHandler={this.onClickHideRelPickerHandler}
|
||||
onClickSelectRelHandler={this.onClickSelectRelHandler}
|
||||
onClickShowRelDetailsHandler={this.onClickShowRelDetailsHandler}
|
||||
/>
|
||||
|
||||
<SDOPicker
|
||||
id="sdo-picker"
|
||||
show={this.store.showSDOPicker}
|
||||
sdos={sdos}
|
||||
onClickHideHandler={this.onClickHideSDOPickerHandler}
|
||||
onClickSelectSDOHandler={this.onClickSelectSDOHandler}
|
||||
/>
|
||||
|
||||
<Growl
|
||||
message={this.store.growlMessage}
|
||||
show={this.store.showGrowl}
|
||||
timer={this.onMessageTimerHandler}
|
||||
/>
|
||||
|
||||
<SubmissionError
|
||||
error={this.store.failedCollection}
|
||||
show={this.store.showSubmissionError}
|
||||
onClickHideHandler={this.onClickHideSubmissionErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} export default inject('store')(observer(Canvas));
|
||||
@@ -1,229 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { toJS } from "mobx";
|
||||
import Tooltip from "react-tooltip";
|
||||
import PropTypes from "prop-types";
|
||||
import Panel from "./ui/panel/Panel";
|
||||
import Slider from "./ui/inputs/Slider";
|
||||
import Text from "./ui/inputs/Text";
|
||||
import TextArea from "./ui/inputs/TextArea";
|
||||
import DateTime from "./ui/inputs/DateTime";
|
||||
import ArraySelector from "./ui/inputs/ArraySelector";
|
||||
import KillChain from "./ui/complex/KillChain";
|
||||
import ExternalReferences from "./ui/complex/ExternalReferences";
|
||||
import CSVInput from "./ui/inputs/CSVInput";
|
||||
import Boolean from "./ui/inputs/Boolean";
|
||||
import GenericObject from "./ui/complex/GenericObject";
|
||||
import ConfirmTextarea from "./ui/complex/ConfirmTextarea";
|
||||
import uuid from "uuid";
|
||||
|
||||
import detailsStyle from './details.scss';
|
||||
|
||||
function importAll(r) {
|
||||
let images = {};
|
||||
r.keys().map((item, index) => { images[item.replace('./', '')] = r(item); });
|
||||
return images;
|
||||
}
|
||||
|
||||
const images = importAll(require.context('../imgs', false, /\.(png|jpe?g|svg)$/));
|
||||
|
||||
@observer
|
||||
export default class Details extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChangeHandler = this.onChangeHandler.bind(this);
|
||||
this.onChangeDateHandler = this.onChangeDateHandler.bind(this);
|
||||
}
|
||||
|
||||
onChangeHandler(event) {
|
||||
this.props.onChangeNodeHandler(event);
|
||||
}
|
||||
|
||||
onChangeDateHandler(property, datetime) {
|
||||
this.props.onChangeDateHandler(property, datetime);
|
||||
}
|
||||
|
||||
render() {
|
||||
const node = toJS(this.props.node);
|
||||
let props = {};
|
||||
let img;
|
||||
let details = [];
|
||||
|
||||
let deleteIcon = <span className="material-icons">delete_forever</span>;
|
||||
|
||||
if (node.properties) {
|
||||
props = node.properties;
|
||||
img = <img src={images[node.img].default} width="30" />;
|
||||
}
|
||||
|
||||
for (let prop in props) {
|
||||
|
||||
let header = <div className="item-header">{prop}
|
||||
<span data-tip={props[prop].description} className="material-icons">info</span>
|
||||
<Tooltip />
|
||||
</div>
|
||||
|
||||
let control = <div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">{props[prop].value}</div>
|
||||
</div>;
|
||||
// If there is no type, we do not want to process. If a "control"
|
||||
// is defined, that indicates special handling of the value.
|
||||
if (props[prop].type && !props[prop].control) {
|
||||
switch (props[prop].type) {
|
||||
case "number":
|
||||
case "string":
|
||||
control = <div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<Text name={prop}
|
||||
value={props[prop].value}
|
||||
onChange={this.onChangeHandler} />
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
case "dts":
|
||||
control = <div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<DateTime name={prop}
|
||||
selected={props[prop].value}
|
||||
onChange={this.onChangeDateHandler} />
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
case "array":
|
||||
control = <ArraySelector vocab={props[prop].vocab}
|
||||
key={prop}
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
onClickHandler={this.props.onClickArrayHandler} />
|
||||
break;
|
||||
case "boolean":
|
||||
control = <div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<Boolean name={prop}
|
||||
selected={props[prop].value}
|
||||
onClick={this.props.onClickBooleanHandler} />
|
||||
</div>
|
||||
</div>
|
||||
break
|
||||
case "object":
|
||||
control = <div className="item" key={prop}>
|
||||
{header}
|
||||
|
||||
</div>
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch (props[prop].control) {
|
||||
case "hidden":
|
||||
control = "";
|
||||
break;
|
||||
case "slider":
|
||||
control = <div className="item slider" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<Slider value={props[prop].value}
|
||||
field={prop}
|
||||
onChangeHandler={this.props.onChangeSliderHandler} />
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
case "csv":
|
||||
control = <div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<CSVInput key={prop}
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
onChangeHandler={this.props.onChangeCSVHandler} />
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
case "killchain":
|
||||
control = <KillChain vocab={props[prop].vocab}
|
||||
node={node}
|
||||
key={prop}
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
onChangeHandler={this.props.onChangePhaseHandler}
|
||||
onClickRemoveHandler={this.props.onClickRemovePhaseHander} />
|
||||
break;
|
||||
case "externalrefs":
|
||||
control = <ExternalReferences node={node}
|
||||
key={prop}
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
|
||||
onChangeERHandler={this.props.onChangeERHandler}
|
||||
onClickDeletePropertyHandler={this.props.onClickDeletePropertyHandler} />
|
||||
break;
|
||||
case "stringselector":
|
||||
control = <ArraySelector vocab={props[prop].vocab}
|
||||
key={prop}
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
onClickHandler={this.props.onClickArrayHandler} />
|
||||
break;
|
||||
case "textarea":
|
||||
control = <div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<TextArea name={prop}
|
||||
value={props[prop].value}
|
||||
onChange={this.onChangeHandler} />
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
case "genericobject":
|
||||
control = <GenericObject name={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
key={uuid()}
|
||||
field={prop}
|
||||
onClickAddObjectHandler={this.props.onClickAddGenericObjectHandler}
|
||||
onClickDeleteObjectHandler={this.props.onClickDeleteGenericObjectHandler}
|
||||
onChangeHandler={this.props.onChangeGenericObjectHandler} />
|
||||
break;
|
||||
case "confirmtextarea":
|
||||
control = <ConfirmTextarea name={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
key={uuid()}
|
||||
field={prop}
|
||||
onClickAddTextHandler={this.props.onClickAddTextHandler} />
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
details.push(
|
||||
control
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel show={this.props.show} onClickHideHandler={this.props.onClickHideHandler}>
|
||||
<div className="details">
|
||||
<div className="header">
|
||||
<div className="title">{img} {node.id}</div>
|
||||
<div className="delete" onClick={this.props.onClickDeleteHandler}>{deleteIcon} <span className="text">Delete</span></div>
|
||||
</div>
|
||||
<div className="body">
|
||||
{details}
|
||||
</div>
|
||||
|
||||
<div className="footer"></div>
|
||||
</div>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
}
|
||||
374
app/src/components/Details.jsx
Normal file
@@ -0,0 +1,374 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { toJS } from 'mobx';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import Panel from './ui/panel/Panel';
|
||||
import Slider from './ui/inputs/Slider';
|
||||
import Text from './ui/inputs/Text';
|
||||
import TextArea from './ui/inputs/TextArea';
|
||||
import DateTime from './ui/inputs/DateTime';
|
||||
import ArraySelector from './ui/inputs/ArraySelector';
|
||||
import KillChain from './ui/complex/KillChain';
|
||||
import ExternalReferences from './ui/complex/ExternalReferences';
|
||||
import CSVInput from './ui/inputs/CSVInput';
|
||||
import Boolean from './ui/inputs/Boolean';
|
||||
import GenericObject from './ui/complex/GenericObject';
|
||||
import ConfirmTextarea from './ui/complex/ConfirmTextarea';
|
||||
import ObjectArray from './ui/complex/ObjectArray';
|
||||
|
||||
import Images from '../imgs/Images';
|
||||
|
||||
import './details.scss';
|
||||
|
||||
class Details extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChangeHandler = this.onChangeHandler.bind(this);
|
||||
this.onChangeDateHandler = this.onChangeDateHandler.bind(this);
|
||||
}
|
||||
|
||||
onChangeHandler(event) {
|
||||
this.props.onChangeNodeHandler(event);
|
||||
}
|
||||
|
||||
onChangeDateHandler(property, datetime) {
|
||||
this.props.onChangeDateHandler(property, datetime);
|
||||
}
|
||||
|
||||
render() {
|
||||
const node = toJS(this.props.node);
|
||||
let props = {};
|
||||
let img;
|
||||
const details = [];
|
||||
|
||||
const deleteIcon = <span className="material-icons">delete_forever</span>;
|
||||
|
||||
if (node.properties) {
|
||||
props = node.properties;
|
||||
img = (
|
||||
<img
|
||||
src={node.customImg ? node.customImg : Images.getImage(node.img)}
|
||||
alt={node.id}
|
||||
width="30"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
for (const prop in props) {
|
||||
const header = (
|
||||
<div className="item-header">
|
||||
{prop}
|
||||
<span
|
||||
data-tooltip-id={`${prop}-tooltip`}
|
||||
className="material-icons"
|
||||
data-tooltip-content={props[prop].description}
|
||||
>
|
||||
info
|
||||
</span>
|
||||
<Tooltip id={`${prop}-tooltip`} />
|
||||
</div>
|
||||
);
|
||||
|
||||
let control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">{props[prop].value}</div>
|
||||
</div>
|
||||
);
|
||||
// If there is no type, we do not want to process. If a "control"
|
||||
// is defined, that indicates special handling of the value.
|
||||
if (props[prop].type && !props[prop].control) {
|
||||
switch (props[prop].type) {
|
||||
case 'number':
|
||||
case 'string':
|
||||
control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<Text
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
onChange={this.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'dts':
|
||||
control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<DateTime
|
||||
name={prop}
|
||||
selected={props[prop].value}
|
||||
onChange={this.onChangeDateHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'array':
|
||||
if (props[prop].vocab) {
|
||||
control = (
|
||||
<ArraySelector
|
||||
vocab={props[prop].vocab}
|
||||
key={prop}
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
onClickHandler={this.props.onClickArrayHandler}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// Get array subtype, possibly nested
|
||||
let refField = props[prop].items;
|
||||
if (Array.isArray(refField)) {
|
||||
refField = refField[0];
|
||||
}
|
||||
const ref = refField.$ref ? refField.$ref : refField.type;
|
||||
|
||||
if (ref === '../common/dictionary.json' || ref === 'object') {
|
||||
control = (
|
||||
<ObjectArray
|
||||
node={node}
|
||||
key={prop}
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
|
||||
onChangeObjectHandler={this.props.onChangeArrayObjectHandler}
|
||||
onClickDeleteArrayObjectHandler={this.props.onClickDeleteArrayObjectHandler}
|
||||
onClickDeleteArrayObjectPropertyHandler={
|
||||
this.props.onClickDeleteArrayObjectPropertyHandler
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
props[prop].control = 'listtextarea';
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<Boolean
|
||||
name={prop}
|
||||
selected={props[prop].value}
|
||||
onClick={this.props.onClickBooleanHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case '../common/dictionary.json':
|
||||
case 'object':
|
||||
control = (
|
||||
<GenericObject
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
key={uuid()}
|
||||
field={prop}
|
||||
onClickAddObjectHandler={
|
||||
this.props.onClickAddGenericObjectHandler
|
||||
}
|
||||
onClickDeleteObjectHandler={
|
||||
this.props.onClickDeleteGenericObjectHandler
|
||||
}
|
||||
onChangeHandler={this.props.onChangeGenericObjectHandler}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (props[prop].$ref && !props[prop].control) {
|
||||
switch (props[prop].$ref) {
|
||||
case '../common/identifier.json':
|
||||
control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<Text
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
onChange={this.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (props[prop].control) {
|
||||
case 'hidden':
|
||||
control = '';
|
||||
break;
|
||||
case 'slider':
|
||||
control = (
|
||||
<div className="item slider" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<Slider
|
||||
value={props[prop].value}
|
||||
field={prop}
|
||||
onChangeHandler={this.props.onChangeSliderHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'csv':
|
||||
control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<CSVInput
|
||||
key={prop}
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
onChangeHandler={this.props.onChangeCSVHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'killchain':
|
||||
control = (
|
||||
<KillChain
|
||||
vocab={props[prop].vocab}
|
||||
node={node}
|
||||
key={prop}
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
onChangeHandler={this.props.onChangePhaseHandler}
|
||||
onClickRemoveHandler={this.props.onClickRemovePhaseHander}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'externalrefs':
|
||||
control = (
|
||||
<ExternalReferences
|
||||
node={node}
|
||||
key={prop}
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
|
||||
onChangeERHandler={this.props.onChangeERHandler}
|
||||
onClickDeleteERHandler={this.props.onClickDeleteERHandler}
|
||||
onClickDeletePropertyHandler={
|
||||
this.props.onClickDeletePropertyHandler
|
||||
}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'stringselector':
|
||||
control = (
|
||||
<ArraySelector
|
||||
vocab={props[prop].vocab}
|
||||
key={prop}
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
onClickHandler={this.props.onClickArrayHandler}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'textarea':
|
||||
control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<TextArea
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
onChange={this.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'listtextarea':
|
||||
control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<CSVInput
|
||||
key={prop}
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
onChangeHandler={this.props.onChangeCSVHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'genericobject':
|
||||
control = (
|
||||
<GenericObject
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
key={uuid()}
|
||||
field={prop}
|
||||
onClickAddObjectHandler={
|
||||
this.props.onClickAddGenericObjectHandler
|
||||
}
|
||||
onClickDeleteArrayObjectHandler={
|
||||
this.props.onClickDeleteGenericObjectHandler
|
||||
}
|
||||
onChangeHandler={this.props.onChangeGenericObjectHandler}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'confirmtextarea':
|
||||
control = (
|
||||
<ConfirmTextarea
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
key={uuid()}
|
||||
field={prop}
|
||||
onClickAddTextHandler={this.props.onClickAddTextHandler}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
details.push(control);
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}
|
||||
>
|
||||
<div className="details">
|
||||
<div className="header">
|
||||
<div className="title">
|
||||
{img}
|
||||
{' '}
|
||||
{node.id}
|
||||
</div>
|
||||
<div className="delete" onClick={this.props.onClickDeleteHandler}>
|
||||
{deleteIcon}
|
||||
{' '}
|
||||
<span className="text">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body">{details}</div>
|
||||
|
||||
<div className="footer" />
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(Details));
|
||||
115
app/src/components/FileImporter.jsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import Panel from './ui/panel/Panel';
|
||||
import FileSelector from './ui/inputs/FileSelector';
|
||||
|
||||
import './details.scss';
|
||||
|
||||
class FileImporter extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onChangeSchemaHandler = this.onChangeSchemaHandler.bind(this);
|
||||
this.onChangeBundleHandler = this.onChangeBundleHandler.bind(this);
|
||||
}
|
||||
|
||||
async onChangeSchemaHandler(event) {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const files = Array.from(event.target.files);
|
||||
files.map((file) => {
|
||||
const fr = new FileReader();
|
||||
fr.readAsText(file, 'UTF-8');
|
||||
fr.onload = (e) => {
|
||||
const { result, } = e.target;
|
||||
this.props.onChangeSchemaHandler(result);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async onChangeBundleHandler(event) {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const files = Array.from(event.target.files);
|
||||
files.map((file) => {
|
||||
const fr = new FileReader();
|
||||
fr.readAsText(file, 'UTF-8');
|
||||
fr.onload = (e) => {
|
||||
const { result, } = e.target;
|
||||
this.props.onChangeBundleHandler(result);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const details = [];
|
||||
|
||||
let header = (
|
||||
<div className="item-header">
|
||||
Import Schemas
|
||||
<span
|
||||
data-tooltip-id="schema-tip"
|
||||
data-tooltip-content="Import custom schema objects from file"
|
||||
className="material-icons"
|
||||
>
|
||||
info
|
||||
</span>
|
||||
<Tooltip id="schema-tip" />
|
||||
</div>
|
||||
);
|
||||
|
||||
let control = (
|
||||
<div className="item" key="schemas">
|
||||
{header}
|
||||
<FileSelector
|
||||
name="schemas"
|
||||
type=".json"
|
||||
multiple
|
||||
onChange={this.onChangeSchemaHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
details.push(control);
|
||||
|
||||
header = (
|
||||
<div className="item-header">
|
||||
Import Bundle
|
||||
<span
|
||||
data-tooltip-id="file-tooltip"
|
||||
data-tooltip-content="Import STIX Bundle from file"
|
||||
className="material-icons"
|
||||
>
|
||||
info
|
||||
</span>
|
||||
<Tooltip id="file-tooltip" />
|
||||
</div>
|
||||
);
|
||||
|
||||
control = (
|
||||
<div className="item" key="bundle">
|
||||
{header}
|
||||
<FileSelector
|
||||
name="bundle"
|
||||
type=".json"
|
||||
multiple={false}
|
||||
onChange={this.onChangeBundleHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
details.push(control);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}
|
||||
>
|
||||
<div className="details">
|
||||
<div className="body">{details}</div>
|
||||
<div className="footer" />
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(FileImporter));
|
||||
155
app/src/components/Flow/Flow.jsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import {
|
||||
React, useState, useEffect, useCallback
|
||||
} from 'react';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
MarkerType,
|
||||
ConnectionMode,
|
||||
Background
|
||||
} from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
import './Flow.scss';
|
||||
|
||||
import FlowNode from './FlowNode';
|
||||
import FlowEdge from './FlowEdge';
|
||||
|
||||
const nodeTypes = {
|
||||
default: FlowNode,
|
||||
};
|
||||
|
||||
const edgeTypes = {
|
||||
default: FlowEdge,
|
||||
};
|
||||
|
||||
const defaultViewport = { x: 0, y: 0, zoom: 1, };
|
||||
|
||||
function Flow(props) {
|
||||
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
const createNode = (node) => {
|
||||
if (!node.position) return;
|
||||
const n = {
|
||||
id: node.id,
|
||||
data: {
|
||||
node
|
||||
},
|
||||
type: 'default',
|
||||
position: { x: node.position.x, y: node.position.y, },
|
||||
style: {
|
||||
width: 50, height: 50, padding: 0, borderColor: 'white',
|
||||
},
|
||||
};
|
||||
setNodes((nds) => nds.concat(n));
|
||||
};
|
||||
|
||||
const createEdge = (source, target, label, id) => {
|
||||
const edge = {
|
||||
id,
|
||||
source,
|
||||
target,
|
||||
sourceHandle: 'center',
|
||||
targetHandle: 'center',
|
||||
label,
|
||||
labelShowBg: false,
|
||||
type: 'default',
|
||||
markerEnd: { type: MarkerType.ArrowClosed, width: 20, height: 20, },
|
||||
};
|
||||
setEdges((eds) => eds.concat(edge));
|
||||
return edge;
|
||||
};
|
||||
|
||||
const renderNodes = () => {
|
||||
setNodes([]);
|
||||
props.nodes.forEach((node) => {
|
||||
createNode(node);
|
||||
});
|
||||
};
|
||||
|
||||
const renderEdges = () => {
|
||||
setEdges([]);
|
||||
props.edges.forEach((edge) => {
|
||||
createEdge(
|
||||
edge.source_ref,
|
||||
edge.target_ref,
|
||||
edge.relationship_type,
|
||||
edge.id
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const rerender = () => {
|
||||
renderNodes();
|
||||
renderEdges();
|
||||
props.setUpdateFlow(false);
|
||||
};
|
||||
|
||||
const onConnect = useCallback((params) => {
|
||||
props.onConnectNodeHandler(params.source, params.target);
|
||||
}, []);
|
||||
|
||||
const onEdgeClick = (event, edge) => {
|
||||
props.onClickRelHandler(edge.id);
|
||||
};
|
||||
|
||||
const onNodeClick = (event, node) => {
|
||||
if (props.groupMode) {
|
||||
props.onClickGroupNodeHandler(node.id);
|
||||
} else {
|
||||
props.onClickHandler(node.id);
|
||||
}
|
||||
};
|
||||
|
||||
const onNodeDragStop = useCallback((event, node) => {
|
||||
event.preventDefault();
|
||||
props.onDragStopNodeHandler(node);
|
||||
}, []);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
const position = reactFlowInstance.screenToFlowPosition({
|
||||
x: event.clientX + 50,
|
||||
y: event.clientY + 50,
|
||||
});
|
||||
props.setMousePosition(position.x, position.y);
|
||||
},
|
||||
[reactFlowInstance]
|
||||
);
|
||||
|
||||
useEffect(rerender, [props.updateFlow]);
|
||||
useEffect(rerender, [props.updateFlow]);
|
||||
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<div className="reactflow-wrapper">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
onInit={setReactFlowInstance}
|
||||
onNodeClick={onNodeClick}
|
||||
onEdgeClick={onEdgeClick}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onDrop={onDrop}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
defaultViewport={defaultViewport}
|
||||
nodeOrigin={[0.5, 0.5]}
|
||||
attributionPosition="bottom-left"
|
||||
nodesDraggable
|
||||
connectionMode={ConnectionMode.Loose}
|
||||
>
|
||||
<Background color="black" variant="dots" />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default Flow;
|
||||
23
app/src/components/Flow/Flow.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
.centerHandle {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.react-flow__handle {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.react-flow__handle:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.centerHandle:hover {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.reactflow-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
140
app/src/components/Flow/FlowEdge.jsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import {
|
||||
BaseEdge, EdgeLabelRenderer, useStore, getBezierPath
|
||||
} from 'reactflow';
|
||||
|
||||
export default function FlowEdge(props) {
|
||||
const getPosition = useStore((store) => {
|
||||
const siblings = [];
|
||||
store.edges.forEach((e) => {
|
||||
if ((e.source === props.source || e.source === props.target)
|
||||
&& (e.target === props.source || e.target === props.target)) {
|
||||
siblings.push(e);
|
||||
}
|
||||
});
|
||||
if (siblings.length === 1) return [0, 1];
|
||||
|
||||
siblings.sort();
|
||||
const index = siblings.map((e) => e.id).indexOf(props.id);
|
||||
|
||||
return [index, siblings.length];
|
||||
});
|
||||
|
||||
const adjustedPosition = (props, position) => {
|
||||
const WIDTH = 50;
|
||||
let { sourceX, } = props;
|
||||
let { sourceY, } = props;
|
||||
let { targetX, } = props;
|
||||
let { targetY, } = props;
|
||||
let source = null;
|
||||
let target = null;
|
||||
|
||||
const isRight = (sourceX - targetX) > WIDTH;
|
||||
const isLeft = (targetX - sourceX) > WIDTH;
|
||||
const isBottom = (sourceY - targetY) > WIDTH;
|
||||
const isTop = (targetY - sourceY) > WIDTH;
|
||||
|
||||
// Calculate connection side base on position
|
||||
if (isBottom) {
|
||||
target = 'bottom';
|
||||
targetY = targetY + WIDTH / 2 + 4;
|
||||
} else if (isTop) {
|
||||
source = 'bottom';
|
||||
sourceY = sourceY + WIDTH / 2 + 4;
|
||||
}
|
||||
|
||||
if (isLeft) {
|
||||
if (source === null) {
|
||||
source = 'right';
|
||||
sourceX += WIDTH / 2;
|
||||
}
|
||||
if (target === null) {
|
||||
target = 'left';
|
||||
targetX -= WIDTH / 2;
|
||||
}
|
||||
} else if (isRight) {
|
||||
if (source === null) {
|
||||
source = 'left';
|
||||
sourceX -= WIDTH / 2;
|
||||
}
|
||||
if (target === null) {
|
||||
target = 'right';
|
||||
targetX += WIDTH / 2;
|
||||
}
|
||||
} else {
|
||||
if (source === null) {
|
||||
source = 'top';
|
||||
sourceY = sourceY - WIDTH / 2 + 4;
|
||||
}
|
||||
if (target === null) {
|
||||
target = 'top';
|
||||
targetY = targetY - WIDTH / 2 + 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust edge positioning to prevent overlap
|
||||
const index = position[0] + 1;
|
||||
const partitions = position[1];
|
||||
const sectionWidth = WIDTH / partitions;
|
||||
const offset = index * sectionWidth - (sectionWidth / 2);
|
||||
if (source === 'top' || source === 'bottom') {
|
||||
sourceX = (sourceX - WIDTH / 2) + offset;
|
||||
} else if (target === 'left' || target === 'right') {
|
||||
sourceY = (sourceY - WIDTH / 2) + offset;
|
||||
}
|
||||
|
||||
if (target === 'top' || target === 'bottom') {
|
||||
targetX = (targetX - WIDTH / 2) + offset;
|
||||
} else if (target === 'left' || target === 'right') {
|
||||
targetY = (targetY - WIDTH / 2) + offset;
|
||||
}
|
||||
|
||||
return {
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: source,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition: target,
|
||||
};
|
||||
};
|
||||
|
||||
const getLabel = (props, labelX, labelY) => {
|
||||
const yt = props.targetY;
|
||||
const ys = props.sourceY;
|
||||
const xt = props.targetX;
|
||||
const xs = props.sourceX;
|
||||
|
||||
let rotation;
|
||||
if (xt === xs) {
|
||||
rotation = (yt > ys) ? '90deg' : '-90deg';
|
||||
} else {
|
||||
rotation = `${Math.atan((yt - ys) / (xt - xs))}rad`;
|
||||
}
|
||||
|
||||
return (
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px) rotate(${rotation}) `,
|
||||
}}
|
||||
>
|
||||
{props.label}
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
);
|
||||
};
|
||||
|
||||
const adjusted = adjustedPosition(props, getPosition);
|
||||
const [path, labelX, labelY] = getBezierPath(adjusted);
|
||||
const label = getLabel(props, labelX, labelY);
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge path={path} {...props} markerEnd={props.markerEnd} />
|
||||
;
|
||||
{label}
|
||||
</>
|
||||
);
|
||||
}
|
||||
72
app/src/components/Flow/FlowNode.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import Images from '../../imgs/Images';
|
||||
|
||||
import './FlowNode.scss';
|
||||
|
||||
export default class FlowNode extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selected: false,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { node, } = this.props.data;
|
||||
let display = node.id.split('--')[0];
|
||||
const border = node.selected ? 'solid 1px blue' : '';
|
||||
if (node.properties.name && node.properties.name.value) {
|
||||
display = node.properties.name.value;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
backgroundSize: 'contain',
|
||||
backgroundImage: `url(${node.customImg ? node.customImg : Images.getImage(node.img)})`,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
border: `${border}`,
|
||||
}}
|
||||
/>
|
||||
<Handle
|
||||
className="centerHandle"
|
||||
id="center"
|
||||
type="source"
|
||||
isConnectable={this.props.isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
position={Position.Left}
|
||||
id="left"
|
||||
type="source"
|
||||
isConnectable={this.props.isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
position={Position.Right}
|
||||
id="right"
|
||||
type="source"
|
||||
isConnectable={this.props.isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
position={Position.Top}
|
||||
id="top"
|
||||
type="source"
|
||||
isConnectable={this.props.isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
position={Position.Bottom}
|
||||
id="bottom"
|
||||
type="source"
|
||||
isConnectable={this.props.isConnectable}
|
||||
/>
|
||||
|
||||
<div className="nodeLabel">
|
||||
{display}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
7
app/src/components/Flow/FlowNode.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
@import '../../defaults';
|
||||
|
||||
.nodeLabel {
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
bottom: -45px;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import PropTypes from "prop-types";
|
||||
import Panel from "./ui/panel/Panel";
|
||||
import Button from "./ui/button/Button";
|
||||
import TextArea from "./ui/inputs/TextArea";
|
||||
|
||||
|
||||
import detailsStyle from './json-paste.scss';
|
||||
|
||||
@observer
|
||||
export default class JsonPaste extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Panel show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}>
|
||||
<div className="json-paste">
|
||||
|
||||
<div className="paste-area">
|
||||
<TextArea onChange={this.props.onChangeJSONPasteHandler} value={this.props.value} />
|
||||
</div>
|
||||
|
||||
<div className="json-controls">
|
||||
<Button cls="def standard json-copy" text="Load" onClick={this.props.onClickJSONPasteHandler}>
|
||||
<i className="material-icons">add</i>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import PropTypes from "prop-types";
|
||||
import Panel from "./ui/panel/Panel";
|
||||
import Button from "./ui/button/Button";
|
||||
|
||||
|
||||
import detailsStyle from './json-viewer.scss';
|
||||
|
||||
@observer
|
||||
export default class JsonViewer extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onClickCopyJSONHandler = this.onClickCopyJSONHandler.bind(this);
|
||||
}
|
||||
|
||||
onClickCopyJSONHandler() {
|
||||
let me = this;
|
||||
let range = document.createRange();
|
||||
let message = "JSON Copied to Clipboard";
|
||||
|
||||
range.selectNode(
|
||||
document.getElementById("json-content")
|
||||
);
|
||||
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(range);
|
||||
document.execCommand("copy");
|
||||
|
||||
window.getSelection().removeAllRanges();
|
||||
|
||||
this.props.onClickShowGrowlHandler(message);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Panel show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}>
|
||||
<div className="json-viewer">
|
||||
<div className="json-content">
|
||||
<pre id="json-content">{JSON.stringify(this.props.json, null, 2)}</pre>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="json-controls">
|
||||
<Button cls="def standard json-copy" text="Copy" onClick={this.onClickCopyJSONHandler}>
|
||||
<i className="material-icons">file_copy</i>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
let LeaderLine = require("leader-line");
|
||||
|
||||
|
||||
//module.exports = LeaderLine;
|
||||
//const singleton = new LeaderLine();
|
||||
|
||||
|
||||
|
||||
//export default LeaderLine
|
||||
@@ -1,88 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { toJS } from "mobx";
|
||||
import PropTypes from "prop-types";
|
||||
import classNames from "classnames";
|
||||
|
||||
import nodeStyle from './node.scss';
|
||||
|
||||
function importAll(r) {
|
||||
let images = {};
|
||||
r.keys().map((item, index) => { images[item.replace('./', '')] = r(item); });
|
||||
return images;
|
||||
}
|
||||
|
||||
const images = importAll(require.context('../imgs', false, /\.(png|jpe?g|svg)$/));
|
||||
|
||||
@observer
|
||||
export default class Node extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onDragStartHandler = this.onDragStartHandler.bind(this);
|
||||
this.onDragOverHandler = this.onDragOverHandler.bind(this);
|
||||
this.onDropHandler = this.onDropHandler.bind(this);
|
||||
this.onDragEndHandler = this.onDragEndHandler.bind(this);
|
||||
this.onDragLeaveHandler = this.onDragLeaveHandler.bind(this);
|
||||
}
|
||||
|
||||
onClickHandler(node) {
|
||||
this.props.onClickHandler(node);
|
||||
}
|
||||
|
||||
onDragStartHandler(event) {
|
||||
event.dataTransfer.setData("node", JSON.stringify(this.props.n));
|
||||
this.props.onDragStartHandler(event);
|
||||
}
|
||||
|
||||
onDragEndHandler(event) {
|
||||
this.props.onDragEndHandler(event);
|
||||
}
|
||||
|
||||
onDragOverHandler(event) {
|
||||
event.preventDefault();
|
||||
this.props.onDragOverNodeHandler(event, this.props.n);
|
||||
}
|
||||
|
||||
onDropHandler(event) {
|
||||
event.preventDefault();
|
||||
this.props.onDropOnNodeHandler(this.props.n);
|
||||
this.props.resetBorders();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
onDragLeaveHandler(event) {
|
||||
this.props.resetBorders();
|
||||
}
|
||||
|
||||
render() {
|
||||
const node = this.props.n;
|
||||
let hide = true;
|
||||
|
||||
const cls = classNames({
|
||||
node: true,
|
||||
'hide-node': hide
|
||||
});
|
||||
|
||||
let display = node.id.split("--")[0];
|
||||
|
||||
if (node.properties.name && node.properties.name.value) {
|
||||
display = node.properties.name.value;
|
||||
}
|
||||
|
||||
return (
|
||||
<div id={node.id}
|
||||
className={cls}
|
||||
draggable="true"
|
||||
onClick={() => this.onClickHandler(node)}
|
||||
onDragStart={this.onDragStartHandler}
|
||||
onDragEnd={this.onDragEndHandler}
|
||||
onDragOver={this.onDragOverHandler}
|
||||
onDrop={this.onDropHandler}
|
||||
onDragLeave={this.onDragLeaveHandler}>
|
||||
<img src={images[node.img].default} draggable="false" /> {display}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { toJS } from "mobx";
|
||||
import PropTypes from "prop-types";
|
||||
import Panel from "./ui/panel/Panel";
|
||||
|
||||
import relationshipPickerStyle from './relationship-picker.scss';
|
||||
|
||||
function importAll(r) {
|
||||
let images = {};
|
||||
r.keys().map((item, index) => { images[item.replace('./', '')] = r(item); });
|
||||
return images;
|
||||
}
|
||||
|
||||
const images = importAll(require.context('../imgs', false, /\.(png|jpe?g|svg)$/));
|
||||
|
||||
@observer
|
||||
export default class RelationshipPicker extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onClickSelectRelHandler(relationship) {
|
||||
this.props.onClickSelectRelHandler(relationship);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Panel show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}>
|
||||
<div className="relationship-picker">
|
||||
<div className="header"><img src={images["relationship.png"].default} width="20" /> Possible Relationships</div>
|
||||
<div className="content">
|
||||
{
|
||||
this.props.relationships.map(relationship => {
|
||||
|
||||
let src = relationship.source_ref.split("--")[0];
|
||||
let target = relationship.target_ref.split("--")[0];
|
||||
let srcImg = `${src}.png`;
|
||||
let targetImg = `${target}.png`;
|
||||
|
||||
if (!images[srcImg]) {
|
||||
images[srcImg] = {};
|
||||
images[srcImg].default = "imgs/3536f3f7f55d746d1a9eac4ca5073246.png";
|
||||
}
|
||||
|
||||
if (!images[targetImg]) {
|
||||
images[targetImg] = {};
|
||||
images[targetImg].default = "imgs/3536f3f7f55d746d1a9eac4ca5073246.png";
|
||||
}
|
||||
|
||||
if (relationship.subTarget) {
|
||||
target = relationship.subTarget;
|
||||
}
|
||||
|
||||
return <div className="item" key={relationship.id}
|
||||
onClick={() => this.onClickSelectRelHandler(relationship)}>
|
||||
<img className="src-image" src={images[srcImg].default} width="20" /> {src}
|
||||
<span className="rel-type"> {relationship.relationship_type} </span>
|
||||
{target} <img className="target-image" src={images[targetImg].default} width="20" />
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import PropTypes from "prop-types";
|
||||
import Panel from "./ui/panel/Panel";
|
||||
import Button from "./ui/button/Button";
|
||||
|
||||
import seStyle from './submission-error.scss';
|
||||
|
||||
function importAll(r) {
|
||||
let images = {};
|
||||
r.keys().map((item, index) => { images[item.replace('./', '')] = r(item); });
|
||||
return images;
|
||||
}
|
||||
|
||||
const images = importAll(require.context('../imgs', false, /\.(png|jpe?g|svg)$/));
|
||||
|
||||
@observer
|
||||
export default class SubmissionError extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let error = this.props.error.length ? this.props.error : [];
|
||||
let errorStructure = {};
|
||||
let msg = [];
|
||||
|
||||
this.props.error.map((item, i) => {
|
||||
if (!errorStructure.hasOwnProperty(item.node)) {
|
||||
errorStructure[item.node] = {};
|
||||
errorStructure[item.node]["details"] = [];
|
||||
errorStructure[item.node]["img"] = item.img;
|
||||
errorStructure[item.node]["details"].push({
|
||||
msg: item.msg,
|
||||
property: item.property
|
||||
});
|
||||
} else {
|
||||
errorStructure[item.node]["details"].push({
|
||||
msg: item.msg,
|
||||
property: item.property
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
for (let item in errorStructure) {
|
||||
let details = [];
|
||||
|
||||
if (errorStructure[item].details) {
|
||||
errorStructure[item].details.map((detail) => {
|
||||
details.push(
|
||||
<div key={detail.property} className="row"><span>{detail.property}:</span> {detail.msg}</div>
|
||||
)
|
||||
});
|
||||
|
||||
msg.push(
|
||||
<div key={item}>
|
||||
<div className="header"><img src={images[errorStructure[item].img].default} width="30" /> {item}</div>
|
||||
<div className="rows-container">
|
||||
{details}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Panel show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}>
|
||||
<div className="submission-error">
|
||||
{msg}
|
||||
</div>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
}
|
||||
78
app/src/components/SubmissionError.jsx
Normal file
@@ -0,0 +1,78 @@
|
||||
/* eslint-disable react/prefer-stateless-function */
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import Panel from './ui/panel/Panel';
|
||||
import Images from '../imgs/Images';
|
||||
|
||||
import './SubmissionError.scss';
|
||||
|
||||
class SubmissionError extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const errorStructure = {};
|
||||
const msg = [];
|
||||
|
||||
this.props.error.map((item, i) => {
|
||||
if (!errorStructure.hasOwnProperty(item.node)) {
|
||||
errorStructure[item.node] = {};
|
||||
errorStructure[item.node].details = [];
|
||||
errorStructure[item.node].img = item.img;
|
||||
errorStructure[item.node].details.push({
|
||||
msg: item.msg,
|
||||
property: item.property,
|
||||
});
|
||||
} else {
|
||||
errorStructure[item.node].details.push({
|
||||
msg: item.msg,
|
||||
property: item.property,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (const item in errorStructure) {
|
||||
const details = [];
|
||||
|
||||
if (errorStructure[item].details) {
|
||||
errorStructure[item].details.map((detail) => {
|
||||
details.push(
|
||||
<div key={detail.property} className="row">
|
||||
<span>
|
||||
{detail.property}
|
||||
:
|
||||
</span>
|
||||
{' '}
|
||||
{detail.msg}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
msg.push(
|
||||
<div key={item}>
|
||||
<div className="header">
|
||||
<img src={Images.getImage(errorStructure[item].img)} width="30" />
|
||||
{' '}
|
||||
{item}
|
||||
</div>
|
||||
<div className="rows-container">
|
||||
{details}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}
|
||||
>
|
||||
<div className="submission-error">
|
||||
{msg}
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(SubmissionError));
|
||||
32
app/src/components/bundle/JsonPaste.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
/* eslint-disable react/prefer-stateless-function */
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import Panel from '../ui/panel/Panel';
|
||||
import Button from '../ui/button/Button';
|
||||
import TextArea from '../ui/inputs/TextArea';
|
||||
|
||||
import './JsonPaste.scss';
|
||||
|
||||
class JsonPaste extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}
|
||||
>
|
||||
<div className="json-paste">
|
||||
|
||||
<div className="paste-area">
|
||||
<TextArea onChange={this.props.onChangeJSONPasteHandler} value={this.props.value} />
|
||||
</div>
|
||||
|
||||
<div className="json-controls">
|
||||
<Button cls="def standard json-copy" text="Load" onClick={this.props.onClickJSONPasteHandler}>
|
||||
<i className="material-icons">add</i>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(JsonPaste));
|
||||
52
app/src/components/bundle/JsonViewer.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import Panel from '../ui/panel/Panel';
|
||||
import Button from '../ui/button/Button';
|
||||
|
||||
import './JsonViewer.scss';
|
||||
|
||||
class JsonViewer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onClickCopyJSONHandler = this.onClickCopyJSONHandler.bind(this);
|
||||
}
|
||||
|
||||
onClickCopyJSONHandler() {
|
||||
const range = document.createRange();
|
||||
const message = 'JSON Copied to Clipboard';
|
||||
|
||||
range.selectNode(
|
||||
document.getElementById('json-content')
|
||||
);
|
||||
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(range);
|
||||
document.execCommand('copy');
|
||||
|
||||
window.getSelection().removeAllRanges();
|
||||
|
||||
this.props.onClickShowGrowlHandler(message);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}
|
||||
>
|
||||
<div className="json-viewer">
|
||||
<div className="json-content">
|
||||
<pre id="json-content">{this.props.json}</pre>
|
||||
</div>
|
||||
|
||||
<div className="json-controls">
|
||||
<Button cls="def standard json-copy" text="Copy" onClick={this.onClickCopyJSONHandler}>
|
||||
<i className="material-icons">file_copy</i>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(JsonViewer));
|
||||
38
app/src/components/menus/BottomMenu.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
/* eslint-disable react/prefer-stateless-function */
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import MenuItem from './MenuItem';
|
||||
import Images from '../../imgs/Images';
|
||||
|
||||
import './BottomMenu.scss';
|
||||
|
||||
class BottomMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="menu">
|
||||
<div className="row">
|
||||
{
|
||||
this.props.objects.map((object, i) => {
|
||||
if (object.active) {
|
||||
return (
|
||||
<MenuItem
|
||||
key={i}
|
||||
object={object}
|
||||
image={object.customImg ? object.customImg : Images.getImage(object.img)}
|
||||
onDragStartHandler={this.props.onDragStartHandler}
|
||||
generateNodeID={this.props.generateNodeID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} export default (observer(BottomMenu));
|
||||
@@ -2,13 +2,19 @@
|
||||
|
||||
.menu {
|
||||
position: fixed;
|
||||
width: 90vw;
|
||||
bottom: 20px;
|
||||
right: 25px;
|
||||
overflow-x: scroll;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
width: 40px;
|
||||
padding-right: 10px;
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import MenuItem from "./MenuItem";
|
||||
|
||||
import menuStyle from './menu.scss';
|
||||
|
||||
@observer
|
||||
export default class Menu extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="menu">
|
||||
<div className="row">
|
||||
{
|
||||
this.props.objects.map((o, i) => {
|
||||
if (o.active) {
|
||||
return <MenuItem
|
||||
key={i}
|
||||
o={o}
|
||||
onDragStartHandler={this.props.onDragStartHandler}
|
||||
generateNodeID={this.props.generateNodeID} />
|
||||
}
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { toJS } from "mobx";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import menuStyle from "./menu.scss";
|
||||
|
||||
function importAll(r) {
|
||||
let images = {};
|
||||
r.keys().map((item, index) => { images[item.replace('./', '')] = r(item); });
|
||||
return images;
|
||||
}
|
||||
|
||||
const images = importAll(require.context('../../imgs', false, /\.(png|jpe?g|svg)$/));
|
||||
|
||||
@observer
|
||||
export default class Menu extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onDragStartHandler = this.onDragStartHandler.bind(this);
|
||||
}
|
||||
|
||||
onDragStartHandler(event) {
|
||||
const id = this.props.generateNodeID(this.props.o.prefix);
|
||||
this.props.o.id = id;
|
||||
event.dataTransfer.setData("node", JSON.stringify(this.props.o));
|
||||
|
||||
this.props.onDragStartHandler(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const o = this.props.o;
|
||||
|
||||
return (
|
||||
<div className="menu-item"
|
||||
draggable="true"
|
||||
onDragStart={this.onDragStartHandler}>
|
||||
<img src={images[o.img].default} draggable="false" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
41
app/src/components/menus/MenuItem.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import './BottomMenu.scss';
|
||||
|
||||
class MenuItem extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onDragStartHandler = this.onDragStartHandler.bind(this);
|
||||
}
|
||||
|
||||
onDragStartHandler(event) {
|
||||
const id = this.props.generateNodeID(this.props.object.prefix);
|
||||
this.props.object.id = id;
|
||||
event.dataTransfer.setData('node', JSON.stringify(this.props.object));
|
||||
|
||||
this.props.onDragStartHandler(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { object, } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span data-tooltip-id={`${object.title}-tooltip`} data-tooltip-content={object.title}>
|
||||
<div
|
||||
className="menu-item"
|
||||
draggable="true"
|
||||
onDragStart={this.onDragStartHandler}
|
||||
>
|
||||
<img src={this.props.image} alt={object.title} draggable="false" />
|
||||
|
||||
</div>
|
||||
</span>
|
||||
<Tooltip id={`${object.title}-tooltip`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} export default (observer(MenuItem));
|
||||
@@ -1,39 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import Tooltip from "react-tooltip";
|
||||
|
||||
import menuStyle from './top-menu.scss';
|
||||
|
||||
@observer
|
||||
export default class TopMenu extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="top-menu">
|
||||
<div className="row">
|
||||
<div data-tip={"Paste JSON"} className="json-paste-btn menu-item-medium" onClick={this.props.onClickShowJsonPasteHandler}>
|
||||
{"{ + }"}
|
||||
</div>
|
||||
|
||||
<div data-tip={"View JSON"} className="json-btn menu-item-small" onClick={this.props.onClickShowJsonHandler}>
|
||||
{"{ }"}
|
||||
</div>
|
||||
|
||||
<div data-tip={"Clear JSON"} className="reset-btn menu-item" onClick={this.props.onClickResetHandler}>
|
||||
<span className="i material-icons">refresh</span> Reset
|
||||
</div>
|
||||
|
||||
<div data-tip={"Submit JSON"} className="reset-btn menu-item" onClick={this.props.onClickSubmitHandler}>
|
||||
<span className="i material-icons">add</span> Submit
|
||||
</div>
|
||||
|
||||
<Tooltip />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
154
app/src/components/menus/TopMenu.jsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
|
||||
import LabeledText from '../ui/inputs/LabeledText';
|
||||
|
||||
import './TopMenu.scss';
|
||||
|
||||
class TopMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.updateCreatorID = this.updateCreatorID.bind(this);
|
||||
this.flipGroupMode = this.flipGroupMode.bind(this);
|
||||
this.submitGroup = this.submitGroup.bind(this);
|
||||
}
|
||||
|
||||
updateCreatorID(event) {
|
||||
const creatorID = event.currentTarget.value;
|
||||
this.props.onChangeCreatorIDHandler(creatorID);
|
||||
}
|
||||
|
||||
flipGroupMode() {
|
||||
const curr = this.props.groupMode;
|
||||
this.props.onClickGroupModeHandler(!curr);
|
||||
}
|
||||
|
||||
submitGroup() {
|
||||
this.props.onClickSubmitGroupingHandler();
|
||||
}
|
||||
|
||||
render() {
|
||||
let groupLabel = 'Select';
|
||||
let groupClass = '';
|
||||
let items;
|
||||
|
||||
if (this.props.groupMode) {
|
||||
groupLabel = 'Cancel';
|
||||
groupClass = 'cancel-btn';
|
||||
items = (
|
||||
<div id="myDropdown" className="dropdown-content">
|
||||
<a onClick={this.submitGroup}>Create Group</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const group = (
|
||||
<div className="dropdown">
|
||||
<div
|
||||
data-tooltip-id="select-tooltip"
|
||||
data-tooltip-content="Select Nodes"
|
||||
className={`grouping-btn menu-item ${groupClass}`}
|
||||
onClick={this.flipGroupMode}
|
||||
>
|
||||
{groupLabel}
|
||||
{items}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="top-menu">
|
||||
<div className="row">
|
||||
<div
|
||||
data-tooltip-id="creator-tooltip"
|
||||
data-tooltip-content="Creator ID"
|
||||
className="ctr-input"
|
||||
>
|
||||
<LabeledText
|
||||
name="creator-input"
|
||||
label="Creator ID"
|
||||
value={this.props.creatorID}
|
||||
placeholder="Creator ID"
|
||||
onChange={this.updateCreatorID}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-tooltip-id="paste-tooltip"
|
||||
data-tooltip-content="Paste JSON"
|
||||
className="json-paste-btn menu-item-medium"
|
||||
onClick={this.props.onClickShowJsonPasteHandler}
|
||||
>
|
||||
{'{ + }'}
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="view-tooltip"
|
||||
data-tooltip-content="View JSON"
|
||||
className="json-btn menu-item-small"
|
||||
onClick={this.props.onClickShowJsonHandler}
|
||||
>
|
||||
{'{ }'}
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="schema-tooltip"
|
||||
data-tooltip-content="Paste Schema"
|
||||
className="schema-paste-btn menu-item-medium"
|
||||
onClick={this.props.onClickShowSchemaPasteHandler}
|
||||
>
|
||||
{'{ * }'}
|
||||
</div>
|
||||
<div
|
||||
data-tooltip-id="sdo-tooltip"
|
||||
data-tooltip-content="SDO Extensions"
|
||||
className="sdos-btn menu-item"
|
||||
onClick={this.props.onClickShowSDOPickerHandler}
|
||||
>
|
||||
Exts
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="import-tooltip"
|
||||
data-tooltip-content="Import Data from File"
|
||||
className="reset-btn menu-item"
|
||||
onClick={this.props.onClickShowImporterHandler}
|
||||
>
|
||||
Import
|
||||
</div>
|
||||
{group}
|
||||
<div
|
||||
data-tooltip-id="clear-tooltip"
|
||||
data-tooltip-content="Clear JSON"
|
||||
className="reset-btn menu-item"
|
||||
onClick={this.props.onClickResetHandler}
|
||||
>
|
||||
<span className="i material-icons">refresh</span>
|
||||
{' '}
|
||||
Reset
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="submit-tooltip"
|
||||
data-tooltip-content="Submit JSON"
|
||||
className="reset-btn menu-item"
|
||||
onClick={this.props.onClickSubmitHandler}
|
||||
>
|
||||
<span className="i material-icons">add</span>
|
||||
{' '}
|
||||
Submit
|
||||
</div>
|
||||
|
||||
<Tooltip id="creator-tooltip" />
|
||||
<Tooltip id="paste-tooltip" />
|
||||
<Tooltip id="view-tooltip" />
|
||||
<Tooltip id="schema-tooltip" />
|
||||
<Tooltip id="sdo-tooltip" />
|
||||
<Tooltip id="import-tooltip" />
|
||||
<Tooltip id="clear-tooltip" />
|
||||
<Tooltip id="submit-tooltip" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} export default (observer(TopMenu));
|
||||
133
app/src/components/menus/TopMenu.scss
Normal file
@@ -0,0 +1,133 @@
|
||||
@import '../../defaults';
|
||||
|
||||
.top-menu {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.menu-item-small {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.menu-item-medium {
|
||||
width: 25px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.schema-paste-btn {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px 4px 5px 11px;
|
||||
margin-left: 10px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
background-color: $default-active-bg;
|
||||
}
|
||||
|
||||
.grouping-btn {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px 11px 5px 11px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
background-color: $default-active-bg;
|
||||
width: 55px;
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: -25%;
|
||||
background-color: #f1f1f1;
|
||||
min-width: 160px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-content a {
|
||||
color: black;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {background-color: #ddd;}
|
||||
|
||||
.dropdown:hover .dropdown-content {display: block;}
|
||||
|
||||
|
||||
.cancel-btn:hover {
|
||||
background-color: $error-font;
|
||||
}
|
||||
|
||||
.sdos-btn {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px 11px 5px 11px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
background-color: $default-active-bg;
|
||||
margin-left: 10px;
|
||||
|
||||
.i {
|
||||
width: 20px;
|
||||
vertical-align: middle;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.ctr-input {
|
||||
padding-right: 10px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.json-btn {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px 4px 5px 8px;
|
||||
margin-left: 10px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
background-color: $default-active-bg;
|
||||
}
|
||||
|
||||
.json-paste-btn {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px 4px 5px 8px;
|
||||
margin-left: 5px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
background-color: $default-active-bg;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px 11px 5px 11px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
background-color: $default-active-bg;
|
||||
margin-left: 10px;
|
||||
|
||||
.i {
|
||||
width: 20px;
|
||||
vertical-align: middle;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
@import '../../defaults';
|
||||
|
||||
.top-menu {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.menu-item-small {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.menu-item-medium {
|
||||
width: 25px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.json-btn {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px 4px 5px 11px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
background-color: $default-active-bg;
|
||||
}
|
||||
|
||||
.json-paste-btn {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px 4px 5px 8px;
|
||||
margin-right: 10px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
background-color: $default-active-bg;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px 11px 5px 11px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
background-color: $default-active-bg;
|
||||
margin-left: 10px;
|
||||
|
||||
.i {
|
||||
width: 20px;
|
||||
vertical-align: middle;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
@import '../defaults';
|
||||
|
||||
.node {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
transition: all .5s ease;
|
||||
border: 3px dashed transparent;
|
||||
|
||||
img {
|
||||
width: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-node {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show-node {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ok-border {
|
||||
border: 3px dashed $default-active-bg;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.noway-border {
|
||||
border: 3px dashed $error-font;
|
||||
border-radius: 3px;
|
||||
}
|
||||
183
app/src/components/relationship/RelationshipDetails.jsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import Panel from '../ui/panel/Panel';
|
||||
import Text from '../ui/inputs/Text';
|
||||
import Boolean from '../ui/inputs/Boolean';
|
||||
import Images from '../../imgs/Images';
|
||||
|
||||
import './RelationshipDetails.scss';
|
||||
|
||||
class RelationshipDetails extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
type: 'relates to',
|
||||
x_exclusive: false,
|
||||
};
|
||||
this.onSubmitHandler = this.onSubmitHandler.bind(this);
|
||||
this.onChangeHandler = this.onChangeHandler.bind(this);
|
||||
this.reset = this.reset.bind(this);
|
||||
this.close = this.close.bind(this);
|
||||
}
|
||||
|
||||
onSubmitHandler() {
|
||||
const relationship = this.props.relationships[0];
|
||||
const rel = {
|
||||
type: this.state.type,
|
||||
targetObjectType: relationship.target,
|
||||
x_exclusive: this.state.x_exclusive,
|
||||
};
|
||||
const src = relationship.source_ref;
|
||||
const target = relationship.target_ref;
|
||||
this.props.onClickCreateRelHandler(src, target, rel);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
onChangeHandler(event) {
|
||||
if (event in this.state) {
|
||||
this.setState({ [event]: !this.state[event], });
|
||||
} else {
|
||||
const { value, } = event.target;
|
||||
this.setState({ type: value, });
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.setState({
|
||||
type: 'relates to',
|
||||
x_exclusive: false,
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.reset();
|
||||
this.props.onClickHideHandler();
|
||||
}
|
||||
|
||||
render() {
|
||||
const deleteIcon = <span className="material-icons">delete_forever</span>;
|
||||
let preview;
|
||||
const details = [];
|
||||
if (this.props.relationships.length) {
|
||||
const relationship = this.props.relationships[0];
|
||||
const src = relationship.source_ref.split('--')[0];
|
||||
let target = relationship.target_ref.split('--')[0];
|
||||
|
||||
const srcImg = Images.getImage(`${src}.png`);
|
||||
const targetImg = Images.getImage(`${target}.png`);
|
||||
|
||||
if (relationship.subTarget) {
|
||||
target = relationship.subTarget;
|
||||
}
|
||||
|
||||
preview = (
|
||||
<div className="item" key="preview">
|
||||
<div className="item-header">
|
||||
Preview
|
||||
</div>
|
||||
<div className="preview item-value" key="preview-item">
|
||||
<img className="src-image" alt={src} src={srcImg} width="20" />
|
||||
{' '}
|
||||
{src}
|
||||
<span className="rel-type">
|
||||
{' '}
|
||||
{this.state.type}
|
||||
{' '}
|
||||
</span>
|
||||
{target}
|
||||
{' '}
|
||||
<img className="target-image" alt={target} src={targetImg} width="20" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const descriptions = {
|
||||
type: 'Name of relationship',
|
||||
x_exclusive: 'Relationship exclusive?',
|
||||
|
||||
};
|
||||
|
||||
for (const field in this.state) {
|
||||
const header = (
|
||||
<div className="item-header">
|
||||
{field}
|
||||
<span
|
||||
data-tooltip-id={`${field}-tooltip`}
|
||||
className="material-icons"
|
||||
data-tooltip-content={descriptions[field]}
|
||||
>
|
||||
info
|
||||
</span>
|
||||
<Tooltip id={`${field}-tooltip`} />
|
||||
</div>
|
||||
);
|
||||
|
||||
let control;
|
||||
switch (field) {
|
||||
case 'type':
|
||||
control = (
|
||||
<div className="item" key={field}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<Text
|
||||
name={field}
|
||||
value={this.state.type}
|
||||
onChange={this.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
control = (
|
||||
<div className="item" key={field}>
|
||||
{header}
|
||||
<div className="item-value">
|
||||
<Boolean
|
||||
name={field}
|
||||
selected={this.state[field]}
|
||||
onClick={this.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
details.push(control);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.close}
|
||||
>
|
||||
<div className="details">
|
||||
<div className="header">
|
||||
<div className="title">
|
||||
New Relationship
|
||||
</div>
|
||||
<div className="delete" onClick={this.close}>
|
||||
{deleteIcon}
|
||||
{' '}
|
||||
<span className="text">Discard</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body">
|
||||
{preview}
|
||||
{details}
|
||||
<div
|
||||
className="submit-btn"
|
||||
onClick={this.onSubmitHandler}
|
||||
>
|
||||
<span className="i material-icons">add</span>
|
||||
{' '}
|
||||
Submit
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer" />
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(RelationshipDetails));
|
||||
139
app/src/components/relationship/RelationshipDetails.scss
Normal file
@@ -0,0 +1,139 @@
|
||||
@import '../../defaults';
|
||||
|
||||
.details {
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.header {
|
||||
padding: 20px;
|
||||
font-size: 18px;
|
||||
height: 40px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
background-color: $lt-gray-bg;
|
||||
|
||||
.title {
|
||||
padding-top: 5px;
|
||||
img {
|
||||
vertical-align: middle;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.delete {
|
||||
padding-left: 7px;
|
||||
width: 80px;
|
||||
display: flex;
|
||||
background-color: $default-active-bg;
|
||||
border-radius: 5px;
|
||||
padding-top: 7px;
|
||||
color: $light-font-0;
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 14px;
|
||||
|
||||
span {
|
||||
padding-left: 1px;
|
||||
}
|
||||
|
||||
.text {
|
||||
padding-top: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.delete:hover {
|
||||
background-color: $error-font;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
flex: 1;
|
||||
|
||||
.preview {
|
||||
padding-left: 20px;
|
||||
padding-top: 10px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-family: $default-font-family;
|
||||
line-height: 30px;
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
img.src-image {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
img.target-image {
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.rel-type {
|
||||
color: $default-active-bg;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px 11px 5px 11px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
background-color: $default-active-bg;
|
||||
margin-left: 10px;
|
||||
margin-top: 10px;
|
||||
width: fit-content;
|
||||
|
||||
.i {
|
||||
width: 20px;
|
||||
vertical-align: middle;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
padding-left: 20px;
|
||||
padding-top: 15px;
|
||||
font-weight: normal;
|
||||
|
||||
.horizontal-slider {
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
.item-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
padding-left: 3px;
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slider {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
148
app/src/components/relationship/RelationshipEditor.jsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { toJS } from 'mobx';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import Panel from '../ui/panel/Panel';
|
||||
import Text from '../ui/inputs/Text';
|
||||
import Images from '../../imgs/Images';
|
||||
|
||||
import './RelationshipDetails.scss';
|
||||
|
||||
class RelationshipEditor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
type: this.props.relationship.relationship_type,
|
||||
};
|
||||
this.onSubmitHandler = this.onSubmitHandler.bind(this);
|
||||
this.onChangeHandler = this.onChangeHandler.bind(this);
|
||||
this.reset = this.reset.bind(this);
|
||||
this.close = this.close.bind(this);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.setState({
|
||||
type: this.props.relationship.relationship_type,
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.reset();
|
||||
this.props.onClickHideHandler();
|
||||
}
|
||||
|
||||
onSubmitHandler() {
|
||||
const rel = {
|
||||
type: this.state.type,
|
||||
};
|
||||
|
||||
this.props.onClickEditRelHandler(rel);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
onChangeHandler(event) {
|
||||
if (event in this.state) {
|
||||
this.setState({ [event]: !this.state[event], });
|
||||
} else {
|
||||
const { value, } = event.target;
|
||||
this.setState({ type: value, });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const deleteIcon = <span className="material-icons">delete_forever</span>;
|
||||
let preview;
|
||||
let details;
|
||||
const relationship = toJS(this.props.relationship);
|
||||
let type;
|
||||
if (relationship.type) {
|
||||
let src = relationship.source_ref.split('--')[0];
|
||||
let target = relationship.target_ref.split('--')[0];
|
||||
type = this.state.type;
|
||||
|
||||
if (relationship.x_reverse) {
|
||||
const tmp = src;
|
||||
src = target;
|
||||
target = tmp;
|
||||
}
|
||||
|
||||
const srcImg = Images.getImage(`${src}.png`);
|
||||
const targetImg = Images.getImage(`${target}.png`);
|
||||
|
||||
if (relationship.subTarget) {
|
||||
target = relationship.subTarget;
|
||||
}
|
||||
preview = (
|
||||
<div className="item" key="preview">
|
||||
<div className="item-header">Preview</div>
|
||||
<div className="preview item-value" key="preview-item">
|
||||
<img
|
||||
className="src-image"
|
||||
src={srcImg}
|
||||
width="20"
|
||||
/>
|
||||
{' '}
|
||||
{src}
|
||||
<span className="rel-type">
|
||||
{' '}
|
||||
{type}
|
||||
{' '}
|
||||
</span>
|
||||
{target}
|
||||
{' '}
|
||||
<img
|
||||
className="target-image"
|
||||
src={targetImg}
|
||||
width="20"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
details = (
|
||||
<div className="item" key="type">
|
||||
<div className="item-header">
|
||||
Type
|
||||
<span
|
||||
data-tooltip-id="name-tooltip"
|
||||
className="material-icons"
|
||||
data-tooltip-content="Name of relationship"
|
||||
>
|
||||
info
|
||||
</span>
|
||||
<Tooltip id="name-tooltip" />
|
||||
</div>
|
||||
<div className="item-value">
|
||||
<Text name="type" value={type} onChange={this.onChangeHandler} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Panel show={this.props.show} onClickHideHandler={this.close}>
|
||||
<div className="details">
|
||||
<div className="header">
|
||||
<div className="title">Edit Relationship</div>
|
||||
<div
|
||||
className="delete"
|
||||
onClick={this.props.onClickDeleteRelHandler}
|
||||
>
|
||||
{deleteIcon}
|
||||
{' '}
|
||||
<span className="text">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body">
|
||||
{preview}
|
||||
{details}
|
||||
<div className="submit-btn" onClick={this.onSubmitHandler}>
|
||||
<span className="i material-icons">add</span>
|
||||
{' '}
|
||||
Update
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer" />
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(RelationshipEditor));
|
||||
90
app/src/components/relationship/RelationshipPicker.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import Panel from '../ui/panel/Panel';
|
||||
import Images from '../../imgs/Images';
|
||||
|
||||
import './RelationshipPicker.scss';
|
||||
|
||||
class RelationshipPicker extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onClickSelectRelHandler(relationship) {
|
||||
this.props.onClickSelectRelHandler(relationship);
|
||||
}
|
||||
|
||||
render() {
|
||||
// Do not allow relationship defininition for generic observables
|
||||
let create;
|
||||
if (this.props.relationships.length == 0 || this.props.relationships[0].target_ref) {
|
||||
create = (
|
||||
<div
|
||||
className="item"
|
||||
key="new-relationship"
|
||||
onClick={this.props.onClickShowRelDetailsHandler}
|
||||
>
|
||||
<img className="src-image" src={Images.getImage('add.png')} width="20" />
|
||||
<span className="rel-type"> Create New Relationship </span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}
|
||||
>
|
||||
<div className="relationship-picker">
|
||||
<div className="header">
|
||||
<img src={Images.getImage('relationship.png')} width="20" />
|
||||
{' '}
|
||||
Possible Relationships
|
||||
</div>
|
||||
<div className="content">
|
||||
{create}
|
||||
{
|
||||
this.props.relationships.slice(1).map((relationship) => {
|
||||
let src = relationship.source_ref.split('--')[0];
|
||||
let target = relationship.target_ref.split('--')[0];
|
||||
|
||||
if (relationship.subTarget) {
|
||||
target = relationship.subTarget;
|
||||
}
|
||||
|
||||
if (relationship.x_reverse) {
|
||||
const tmp = src;
|
||||
src = target;
|
||||
target = tmp;
|
||||
}
|
||||
|
||||
const srcImg = Images.getImage(`${src}.png`);
|
||||
const targetImg = Images.getImage(`${target}.png`);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="item"
|
||||
key={relationship.id}
|
||||
onClick={() => this.onClickSelectRelHandler(relationship)}
|
||||
>
|
||||
<img className="src-image" src={srcImg} width="20" />
|
||||
{' '}
|
||||
{src}
|
||||
<span className="rel-type">
|
||||
{' '}
|
||||
{relationship.relationship_type}
|
||||
{' '}
|
||||
</span>
|
||||
{target}
|
||||
{' '}
|
||||
<img className="target-image" src={targetImg} width="20" />
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(RelationshipPicker));
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../defaults";
|
||||
@import "../../defaults";
|
||||
|
||||
.relationship-picker {
|
||||
display: flex;
|
||||
119
app/src/components/schema/SDOEditor.jsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { toJS } from 'mobx';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import Panel from '../ui/panel/Panel';
|
||||
import FileSelector from '../ui/inputs/FileSelector';
|
||||
import Images from '../../imgs/Images';
|
||||
|
||||
import '../details.scss';
|
||||
|
||||
class SDOEditor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChangeHandler = this.onChangeHandler.bind(this);
|
||||
}
|
||||
|
||||
onChangeHandler(event) {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const value = URL.createObjectURL(event.target.files[0]);
|
||||
|
||||
const mutatedEvent = {
|
||||
currentTarget: {
|
||||
name: 'customImg',
|
||||
value,
|
||||
},
|
||||
};
|
||||
this.props.onChangeSDOHandler(mutatedEvent);
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const sdo = toJS(this.props.sdo);
|
||||
let props = {};
|
||||
let img;
|
||||
const details = [];
|
||||
|
||||
const deleteIcon = <span className="material-icons">delete_forever</span>;
|
||||
|
||||
if (sdo.properties) {
|
||||
props = sdo.properties;
|
||||
if (sdo.customImg !== undefined) {
|
||||
img = <img src={sdo.customImg} alt="Custom" width="30" />;
|
||||
} else {
|
||||
img = <img src={Images.getImage(sdo.img)} alt="Custom" width="30" />;
|
||||
}
|
||||
}
|
||||
|
||||
let header = (
|
||||
<div className="item-header">
|
||||
Update Icon
|
||||
<span
|
||||
data-tooltip-id="icon-tooltip"
|
||||
className="material-icons"
|
||||
data-tooltip-content="Set SDO icon to a local image"
|
||||
>
|
||||
info
|
||||
</span>
|
||||
<Tooltip id="icon-tooltip" />
|
||||
</div>
|
||||
);
|
||||
|
||||
let control = (
|
||||
<div className="item" key="icon">
|
||||
{header}
|
||||
<FileSelector
|
||||
name="image"
|
||||
type="image/*"
|
||||
multiple={false}
|
||||
onChange={this.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
details.push(control);
|
||||
|
||||
for (const prop in props) {
|
||||
header = (
|
||||
<div className="item-header">
|
||||
{prop}
|
||||
</div>
|
||||
);
|
||||
|
||||
control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
<div className="item-value">{props[prop].description}</div>
|
||||
</div>
|
||||
);
|
||||
details.push(control);
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}
|
||||
>
|
||||
<div className="details">
|
||||
<div className="header">
|
||||
<div className="title">
|
||||
{img}
|
||||
{' '}
|
||||
{sdo.title}
|
||||
</div>
|
||||
<div className="delete" onClick={this.props.onClickDeleteHandler}>
|
||||
{deleteIcon}
|
||||
{' '}
|
||||
<span className="text">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body">{details}</div>
|
||||
|
||||
<div className="footer" />
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(SDOEditor));
|
||||
52
app/src/components/schema/SDOPicker.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import Panel from '../ui/panel/Panel';
|
||||
import Images from '../../imgs/Images';
|
||||
|
||||
import '../relationship/RelationshipPicker.scss';
|
||||
|
||||
class SDOPicker extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onClickSelectSDOHandler(sdo) {
|
||||
this.props.onClickHideHandler();
|
||||
this.props.onClickSelectSDOHandler(sdo);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}
|
||||
>
|
||||
<div className="relationship-picker">
|
||||
<div className="header">STIX Domain Object (SDO) Extensions</div>
|
||||
<div className="content">
|
||||
{
|
||||
this.props.sdos.map((sdo) => {
|
||||
let img = Images.getImage(sdo.img);
|
||||
if (sdo.customImg) {
|
||||
img = sdo.customImg;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="item"
|
||||
key={sdo.title}
|
||||
onClick={() => this.onClickSelectSDOHandler(sdo)}
|
||||
>
|
||||
<img className="src-image" src={img} width="20" />
|
||||
{' '}
|
||||
{sdo.title}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(SDOPicker));
|
||||
36
app/src/components/schema/SchemaPaste.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
/* eslint-disable react/prefer-stateless-function */
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import Panel from '../ui/panel/Panel';
|
||||
import Button from '../ui/button/Button';
|
||||
import TextArea from '../ui/inputs/TextArea';
|
||||
|
||||
import '../bundle/JsonPaste.scss';
|
||||
|
||||
class SchemaPaste extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}
|
||||
>
|
||||
<div className="json-paste">
|
||||
|
||||
<div className="paste-area">
|
||||
<TextArea onChange={this.props.onChangeSchemaPasteHandler} value={this.props.value} />
|
||||
</div>
|
||||
|
||||
<div className="json-controls">
|
||||
<Button cls="def standard json-copy" text="Load" onClick={this.props.onClickSchemaPasteHandler}>
|
||||
<i className="material-icons">add</i>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
} export default (observer(SchemaPaste));
|
||||
@@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import buttonStyle from './button.scss';
|
||||
|
||||
export default class Button extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onClickHandler = this.onClickHandler.bind(this);
|
||||
}
|
||||
|
||||
onClickHandler() {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const clickHandler = this.props.disabled ? undefined : this.onClickHandler;
|
||||
const classMap = {
|
||||
def: true,
|
||||
disabled: this.props.disabled,
|
||||
};
|
||||
if (this.props.cls) {
|
||||
classMap[this.props.cls] = true;
|
||||
}
|
||||
const classes = classNames(classMap);
|
||||
return (
|
||||
<div>
|
||||
<button className={classes} onClick={clickHandler} >
|
||||
{this.props.children} {this.props.text}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Button.propTypes = {
|
||||
cls: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
45
app/src/components/ui/button/Button.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './button.scss';
|
||||
|
||||
export default class Button extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onClickHandler = this.onClickHandler.bind(this);
|
||||
}
|
||||
|
||||
onClickHandler() {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const clickHandler = this.props.disabled ? undefined : this.onClickHandler;
|
||||
const classMap = {
|
||||
def: true,
|
||||
disabled: this.props.disabled,
|
||||
};
|
||||
if (this.props.cls) {
|
||||
classMap[this.props.cls] = true;
|
||||
}
|
||||
const classes = classNames(classMap);
|
||||
return (
|
||||
<div>
|
||||
<button className={classes} onClick={clickHandler}>
|
||||
{this.props.children}
|
||||
{' '}
|
||||
{this.props.text}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Button.propTypes = {
|
||||
cls: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {observer, inject} from "mobx-react";
|
||||
import classNames from "classnames";
|
||||
import Tooltip from "react-tooltip";
|
||||
import TextArea from "../inputs/TextArea";
|
||||
import uuid from "uuid";
|
||||
|
||||
import confirmTextareaStyle from "./confirmtextarea.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class ConfirmTextarea extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChangeInputHandler = this.onChangeInputHandler.bind(this);
|
||||
this.onClickAddObjectHandler = this.onClickAddObjectHandler.bind(this);
|
||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||
|
||||
this.state = {
|
||||
value: ""
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onChangeInputHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({
|
||||
value: event.currentTarget.value
|
||||
});
|
||||
}
|
||||
|
||||
onClickDeleteHandler(select, idx) {
|
||||
this.props.onClickDeletePropertyHandler(select, idx);
|
||||
}
|
||||
|
||||
onClickAddObjectHandler() {
|
||||
this.props.onClickAddTextHandler(this.props.field, this.state.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const field = this.props.field;
|
||||
const value = this.props.value ? this.props.value : [];
|
||||
const description = this.props.description;
|
||||
const rows = [];
|
||||
|
||||
return (
|
||||
<div className="ct-container">
|
||||
<div className="ct-header">
|
||||
{field}
|
||||
<span data-tip={description} className="material-icons">info</span>
|
||||
<Tooltip />
|
||||
</div>
|
||||
<div className="ct-body">
|
||||
<div className="ct-block-input">
|
||||
<div className="input">
|
||||
<TextArea value={this.state.value}
|
||||
name={field}
|
||||
onChange={this.onChangeInputHandler} />
|
||||
</div>
|
||||
<div className="add-container">
|
||||
<span onClick={this.onClickAddObjectHandler} className="add material-icons">control_point</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ct-output">
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
80
app/src/components/ui/complex/ConfirmTextarea.jsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import TextArea from '../inputs/TextArea';
|
||||
|
||||
import './confirmtextarea.scss';
|
||||
|
||||
class ConfirmTextarea extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChangeInputHandler = this.onChangeInputHandler.bind(this);
|
||||
this.onClickAddObjectHandler = this.onClickAddObjectHandler.bind(this);
|
||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||
|
||||
this.state = {
|
||||
value: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onChangeInputHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({
|
||||
value: event.currentTarget.value,
|
||||
});
|
||||
}
|
||||
|
||||
onClickDeleteHandler(select, idx) {
|
||||
this.props.onClickDeletePropertyHandler(select, idx);
|
||||
}
|
||||
|
||||
onClickAddObjectHandler() {
|
||||
this.props.onClickAddTextHandler(this.props.field, this.state.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { field, } = this.props;
|
||||
const value = this.props.value ? this.props.value : [];
|
||||
const { description, } = this.props;
|
||||
|
||||
return (
|
||||
<div className="ct-container">
|
||||
<div className="ct-header">
|
||||
{field}
|
||||
<span
|
||||
data-tooltip-id={`${field}-tooltip`}
|
||||
className="material-icons"
|
||||
data-tooltip-content={description}
|
||||
>
|
||||
info
|
||||
</span>
|
||||
<Tooltip id={`${field}-tooltip`} />
|
||||
</div>
|
||||
<div className="ct-body">
|
||||
<div className="ct-block-input">
|
||||
<div className="input">
|
||||
<TextArea
|
||||
value={this.state.value}
|
||||
name={field}
|
||||
onChange={this.onChangeInputHandler}
|
||||
/>
|
||||
</div>
|
||||
<div className="add-container">
|
||||
<span onClick={this.onClickAddObjectHandler} className="add material-icons">control_point</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ct-output">
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} export default observer(ConfirmTextarea);
|
||||
@@ -1,123 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {observer, inject} from "mobx-react";
|
||||
import classNames from "classnames";
|
||||
import Tooltip from "react-tooltip";
|
||||
import Text from "../inputs/Text";
|
||||
import uuid from "uuid";
|
||||
|
||||
import killChainStyle from "./externalreferences.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class ExternalReferences extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onClickHandler = this.onClickHandler.bind(this);
|
||||
this.onChangeERHandler = this.onChangeERHandler.bind(this);
|
||||
this.onClickAddHandler = this.onClickAddHandler.bind(this);
|
||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onChangeERHandler(event, value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
onClickAddHandler(input, select, idx) {
|
||||
this.props.onChangeERHandler(input.value, select.options[select.selectedIndex].value, idx);
|
||||
input.value = "";
|
||||
}
|
||||
|
||||
onClickHandler() {
|
||||
const property = this.state.property;
|
||||
const value = this.state.value;
|
||||
}
|
||||
|
||||
onClickDeleteHandler(select, idx) {
|
||||
this.props.onClickDeletePropertyHandler(select, idx);
|
||||
}
|
||||
|
||||
render() {
|
||||
const vocab = this.props.vocab ? this.props.vocab : [];
|
||||
const field = this.props.field;
|
||||
const value = this.props.value;
|
||||
const description = this.props.description;
|
||||
|
||||
const len = value.len;
|
||||
|
||||
return (
|
||||
<div className="er-container">
|
||||
<div className="er-header">
|
||||
{field}
|
||||
<span data-tip={description} className="material-icons">info</span>
|
||||
<span data-tip="Add an External Reference" onClick={() => this.props.onClickAddObjectHandler(field)} className="add material-icons">control_point</span>
|
||||
<Tooltip />
|
||||
</div>
|
||||
<div className="er-body">
|
||||
{
|
||||
value.map((p, i) => {
|
||||
return (
|
||||
<ReferenceBlock key={i}
|
||||
i={i}
|
||||
kv={p}
|
||||
onChangeERHandler={this.onChangeERHandler}
|
||||
onClickAddHandler={this.onClickAddHandler}
|
||||
onClickDeleteHandler={this.onClickDeleteHandler} />
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const ReferenceBlock = (props) => {
|
||||
const blocks = [];
|
||||
const idx = props.i;
|
||||
const selectID = `select-${props.i}`;
|
||||
const inputID = `input-${props.i}`;
|
||||
|
||||
const propValues = [
|
||||
"source_name",
|
||||
"description",
|
||||
"url",
|
||||
"hashes",
|
||||
"external_id"
|
||||
];
|
||||
|
||||
for (let item in props.kv) {
|
||||
let remove = <span onClick={() => props.onClickDeleteHandler(item, props.i)} className="remove material-icons">highlight_off</span>;
|
||||
|
||||
if (item === "source_name") {
|
||||
remove = undefined;
|
||||
}
|
||||
|
||||
blocks.push(
|
||||
<div key={uuid()} className="er-block-row">
|
||||
<div>{item}: {JSON.stringify(props.kv[item])} {remove}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <div className="er-block">
|
||||
<div className="er-block-row">
|
||||
<select id={selectID}>
|
||||
{
|
||||
propValues.map(prop => {
|
||||
return <option key={uuid()} value={prop}>{prop}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
<Text id={inputID} onChange={props.onChangeERHandler} />
|
||||
<span className="add material-icons" onClick={() => props.onClickAddHandler(document.getElementById(inputID), document.getElementById(selectID), props.i)}>control_point</span>
|
||||
</div>
|
||||
{blocks}
|
||||
</div>
|
||||
}
|
||||
166
app/src/components/ui/complex/ExternalReferences.jsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import Text from '../inputs/Text';
|
||||
|
||||
import './externalreferences.scss';
|
||||
|
||||
class ExternalReferences extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onClickHandler = this.onClickHandler.bind(this);
|
||||
this.onChangeERHandler = this.onChangeERHandler.bind(this);
|
||||
this.onClickDeleteERHandler = this.onClickDeleteERHandler.bind(this);
|
||||
this.onClickAddHandler = this.onClickAddHandler.bind(this);
|
||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {}
|
||||
|
||||
onChangeERHandler(event, value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
onClickAddHandler(input, select, idx) {
|
||||
this.props.onChangeERHandler(
|
||||
input.value,
|
||||
select.options[select.selectedIndex].value,
|
||||
idx
|
||||
);
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
onClickHandler() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
onClickDeleteHandler(select, idx) {
|
||||
this.props.onClickDeletePropertyHandler(select, idx);
|
||||
}
|
||||
|
||||
onClickDeleteERHandler(idx) {
|
||||
this.props.onClickDeleteERHandler(idx);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { field, } = this.props;
|
||||
const value = this.props.value ? this.props.value : [];
|
||||
const { description, } = this.props;
|
||||
|
||||
return (
|
||||
<div className="er-container">
|
||||
<div className="er-header">
|
||||
{field}
|
||||
<span
|
||||
data-tooltip-id={`${field}-tooltip`}
|
||||
data-tooltip-content={description}
|
||||
className="material-icons"
|
||||
>
|
||||
info
|
||||
</span>
|
||||
<span
|
||||
data-tooltip-id={`${field}-control-tooltip`}
|
||||
data-tooltip-content="Add an External Reference"
|
||||
onClick={() => this.props.onClickAddObjectHandler(field, ['source_name'])}
|
||||
className="add material-icons"
|
||||
>
|
||||
control_point
|
||||
</span>
|
||||
<Tooltip id={`${field}-tooltip`} />
|
||||
<Tooltip id={`${field}-control-tooltip`} />
|
||||
</div>
|
||||
<div className="er-body">
|
||||
{value.map((p, i) => (
|
||||
<ReferenceBlock
|
||||
key={i}
|
||||
i={i}
|
||||
kv={p}
|
||||
onChangeERHandler={this.onChangeERHandler}
|
||||
onClickDeleteERHandler={this.onClickDeleteERHandler}
|
||||
onClickAddHandler={this.onClickAddHandler}
|
||||
onClickDeleteHandler={this.onClickDeleteHandler}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} export default observer(ExternalReferences);
|
||||
|
||||
function ReferenceBlock(props) {
|
||||
const blocks = [];
|
||||
const idx = props.i;
|
||||
const selectID = `select-${props.i}`;
|
||||
const inputID = `input-${props.i}`;
|
||||
|
||||
const propValues = [
|
||||
'source_name',
|
||||
'description',
|
||||
'url',
|
||||
'hashes',
|
||||
'external_id'
|
||||
];
|
||||
|
||||
for (const item in props.kv) {
|
||||
let remove = (
|
||||
<span
|
||||
onClick={() => props.onClickDeleteHandler(item, props.i)}
|
||||
className="remove material-icons"
|
||||
>
|
||||
highlight_off
|
||||
</span>
|
||||
);
|
||||
|
||||
if (item === 'source_name') {
|
||||
remove = undefined;
|
||||
}
|
||||
|
||||
blocks.push(
|
||||
<div key={uuid()} className="er-block-row">
|
||||
<div>
|
||||
{item}
|
||||
:
|
||||
{JSON.stringify(props.kv[item])}
|
||||
{' '}
|
||||
{remove}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="er-block">
|
||||
<div className="er-block-row">
|
||||
<select id={selectID}>
|
||||
{propValues.map((prop) => (
|
||||
<option key={uuid()} value={prop}>
|
||||
{prop}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Text id={inputID} onChange={props.onChangeERHandler} />
|
||||
<span
|
||||
className="add material-icons"
|
||||
onClick={() => props.onClickAddHandler(
|
||||
document.getElementById(inputID),
|
||||
document.getElementById(selectID),
|
||||
props.i
|
||||
)}
|
||||
>
|
||||
control_point
|
||||
</span>
|
||||
</div>
|
||||
{blocks}
|
||||
<div className="er-block-row">
|
||||
<span
|
||||
className="remove remove-er material-icons"
|
||||
onClick={() => props.onClickDeleteERHandler(idx)}
|
||||
>
|
||||
highlight_off
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {observer, inject} from "mobx-react";
|
||||
import classNames from "classnames";
|
||||
import Tooltip from "react-tooltip";
|
||||
import Text from "../inputs/Text";
|
||||
import uuid from "uuid";
|
||||
|
||||
import killChainStyle from "./genericobject.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class GenericObject extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChangeInputHandler = this.onChangeInputHandler.bind(this);
|
||||
this.onClickAddObjectHandler = this.onClickAddObjectHandler.bind(this);
|
||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||
this.onClickCreateBlankHandler = this.onClickCreateBlankHandler.bind(this);
|
||||
|
||||
this.state = {
|
||||
key: "",
|
||||
value: ""
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onChangeInputHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({
|
||||
[event.currentTarget.name]: event.currentTarget.value
|
||||
});
|
||||
}
|
||||
|
||||
onClickDeleteHandler(select, idx) {
|
||||
this.props.onClickDeletePropertyHandler(select, idx);
|
||||
}
|
||||
|
||||
onClickCreateBlankHandler() {
|
||||
this.setState({
|
||||
key: "",
|
||||
value: ""
|
||||
});
|
||||
}
|
||||
|
||||
onClickAddObjectHandler() {
|
||||
const o = {};
|
||||
|
||||
o[this.state.key] = this.state.value;
|
||||
|
||||
this.props.onClickAddObjectHandler(this.props.field, o);
|
||||
}
|
||||
|
||||
render() {
|
||||
const field = this.props.field;
|
||||
const value = this.props.value ? this.props.value : [];
|
||||
const description = this.props.description;
|
||||
const rows = [];
|
||||
|
||||
for (let key in value) {
|
||||
rows.push(
|
||||
<ExtBlocks
|
||||
key={uuid()}
|
||||
v={value[key]}
|
||||
k={key}
|
||||
field={field}
|
||||
onClickDeleteHandler={this.props.onClickDeleteObjectHandler} />
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="go-container">
|
||||
<div className="go-header">
|
||||
{field}
|
||||
<span data-tip={description} className="material-icons">info</span>
|
||||
<Tooltip />
|
||||
</div>
|
||||
<div className="go-body">
|
||||
|
||||
<div className="go-block-input">
|
||||
<div className="input">
|
||||
<Text name="key" value={this.state.key} onChange={this.onChangeInputHandler} />
|
||||
</div>
|
||||
<div className="input">
|
||||
<Text name="value" value={this.state.value} onChange={this.onChangeInputHandler} />
|
||||
</div>
|
||||
<div className="add-container">
|
||||
<span onClick={this.onClickAddObjectHandler} className="add material-icons">control_point</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{rows}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const ExtBlocks = (props) => {
|
||||
let v = props.v;
|
||||
|
||||
if (typeof props.v === "object") {
|
||||
v = JSON.stringify(props.v);
|
||||
}
|
||||
|
||||
return <div className="go-block">
|
||||
<div className="go-block-row">{props.k}: {v} <span onClick={() => props.onClickDeleteHandler(props.field, props.k)} className="remove material-icons">highlight_off</span></div>
|
||||
</div>
|
||||
}
|
||||