Feature overhaul

This commit is contained in:
riplehk1
2024-09-05 10:50:52 -04:00
parent a8969b53ba
commit 0ded3b0f5b
193 changed files with 12779 additions and 23486 deletions

BIN
app/.DS_Store vendored

Binary file not shown.

20
app/.eslintrc.json Normal file
View 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
View 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?

View File

@@ -1 +0,0 @@
export default '';

View File

@@ -1,3 +0,0 @@
import idObj from 'identity-obj-proxy';
export default idObj;

View File

@@ -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);
});
});

View File

@@ -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,
};
};

View File

@@ -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*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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.
*/

File diff suppressed because one or more lines are too long

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}

View File

@@ -1,9 +0,0 @@
{
"extends": [
"config:base"
],
"automerge": true,
"major": {
"automerge": false
}
}

View File

@@ -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

Binary file not shown.

View File

@@ -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;

View File

@@ -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>
)
}
}

View 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));

View File

@@ -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>
)
}
}

View 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));

View 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));

View 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;

View 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%;
}

View 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}
</>
);
}

View 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>
</>
);
}
}

View File

@@ -0,0 +1,7 @@
@import '../../defaults';
.nodeLabel {
overflow: visible;
position: relative;
bottom: -45px;
}

View File

@@ -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>
)
}
}

View File

@@ -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>
)
}
}

View File

@@ -1,9 +0,0 @@
let LeaderLine = require("leader-line");
//module.exports = LeaderLine;
//const singleton = new LeaderLine();
//export default LeaderLine

View File

@@ -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>
)
}
}

View File

@@ -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>
)
}
}

View File

@@ -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>
)
}
}

View 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));

View 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));

View 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));

View 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));

View File

@@ -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;

View File

@@ -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>
)
}
}

View File

@@ -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>
)
}
}

View 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));

View File

@@ -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>
)
}
}

View 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));

View 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;
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}

View 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));

View 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;
}
}

View 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));

View 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));

View File

@@ -1,4 +1,4 @@
@import "../defaults";
@import "../../defaults";
.relationship-picker {
display: flex;

View 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));

View 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));

View 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));

View File

@@ -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
};

View 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,
};

View File

@@ -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>
)
}
}

View 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);

View File

@@ -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>
}

View 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>
);
}

View File

@@ -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>
}

Some files were not shown because too many files have changed in this diff Show More