initial commit
104
.gitignore
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
31
.readme
Normal file
@@ -0,0 +1,31 @@
|
||||
# STIX 2[x] Drag and Drop Builder
|
||||
|
||||
# Definitions
|
||||
|
||||
The definitions are a direct copy from the OASIS schemas repository without mutation.
|
||||
|
||||
# Definition Adapters
|
||||
|
||||
The definition adapters are a way to mutate the definitions to help control the flow of the visualization. All adapters inherit the Base.js adapter where much of the initial mutating happens.
|
||||
|
||||
# Control Property
|
||||
|
||||
The control property can be used to help extend custom ways to display and/or interact with the properties in the details panel. Some properties default based on their type but if more complex or unique controls are required, the control property is the way to extend functionality.
|
||||
|
||||
Current controls:
|
||||
- hidden: Obviously hides the property in the details panel.
|
||||
- literal: Outputs values as is with a control or input to edit.
|
||||
- slider: Custom slider-bar control.
|
||||
- csv: The csv control will take a comma separated list of values in a text control and split them into an array for the object property values.
|
||||
- killchain: Used to select complex arrays.
|
||||
- externalrefs: Used to build complex objects.
|
||||
- stringselector: Works like the array selector but only allows for selecting a single value to populate a string.
|
||||
- textarea: Allows for cleaner input of larger text amounts.
|
||||
|
||||
# Hoisting Vocabs
|
||||
|
||||
In the definitions specific to an object, I hoist the vocabs onto the property it belongs to. This makes it seamless to pass along to the array control used to select options.
|
||||
|
||||
Specific vocab notes
|
||||
|
||||
- labels: there are placeholder values located in definition-adapters/Base.js. This can easily be updated to reflect your sharing groups standard list for each object or even hidden with the `control` property.
|
||||
BIN
app/.DS_Store
vendored
Normal file
21
app/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Hashem Khalifa
|
||||
|
||||
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.
|
||||
1
app/__mocks__/fileMock.js
Normal file
@@ -0,0 +1 @@
|
||||
export default '';
|
||||
3
app/__mocks__/styleMock.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import idObj from 'identity-obj-proxy';
|
||||
|
||||
export default idObj;
|
||||
17
app/__tests__/app.test.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
47
app/babel.config.js
Normal file
@@ -0,0 +1,47 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
45
app/build/css/main.css
Normal file
@@ -0,0 +1,45 @@
|
||||
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}.node img{width:45px}.hide-node{display:none}.show-node{display:block}.ok-border{border:3px dashed #46a0f5}.noway-border{border:3px dashed #d31d18}
|
||||
|
||||
.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%}.json-viewer pre{flex:1;overflow:scroll;overflow-x:hidden;padding:5px 0px 0px 10px;font-family:courier;font-size:13px}.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*/
|
||||
1
app/build/css/main.css.map
Normal file
6
app/build/css/vendors.css
Normal file
1
app/build/css/vendors.css.map
Normal file
BIN
app/build/imgs/05fc68e8e9705edcf46b48f81e80c07b.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/build/imgs/06856678ce1a47758114a39e37a279dc.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
app/build/imgs/082d284458449f85461c91ddc729b4fa.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
app/build/imgs/1b9c59d2fc6707ab4f13d7a389f6aaf5.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
app/build/imgs/1e5a63b3472b583b4a730f013ec5b10d.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/build/imgs/2106b0ef97949dda5ea996d4b8fe4b08.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
app/build/imgs/29bb5c86a22d42aba10b6ed9ce8e85c6.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
app/build/imgs/2cea2cba2c5e1348efe72e3abbf4eb74.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
app/build/imgs/2de8f879351e33a41cc9a7006d54bfda.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/build/imgs/3536f3f7f55d746d1a9eac4ca5073246.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
app/build/imgs/389852e34bc3b08bdfef2c8915cae2c7.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
app/build/imgs/47cf894c6775c06f5c3f78d86e36f519.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
app/build/imgs/4ac0232853de7d5d575cec38c2f6e0b6.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
app/build/imgs/4ac356832f4c6a00fad8265aa697e9db.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
app/build/imgs/4f2f7c1630b4a591f198768620c86997.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
app/build/imgs/65221f779a26036fd1ba3b78b0012e99.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
app/build/imgs/75739a5825c4c03a135ec0ca87019917.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
app/build/imgs/80b3f9edfc9d2d7b718c4caaf78cd3bf.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
app/build/imgs/919f7d1af774d980da11a1725f2c179b.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
app/build/imgs/a7352377424d00d6a7f5778adc045c97.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/build/imgs/a889bfe1458d2191318ca350a6f86f02.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
app/build/imgs/abd9af55a4279c4739e5e439d2538eeb.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
app/build/imgs/b4c195d9d53719dcc70f7eed78a9369b.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
app/build/imgs/c7066e3c6eea8aec2f175fef7239533e.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/build/imgs/ccf90d26d6ed1ba93cf3888d0453290d.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
app/build/imgs/f635b56b60afd46f212a2d109b96df60.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
20
app/build/index.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!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.5fefa56713048225fb7f.js" async></script><script type="text/javascript" src="js/vendors.d617334cf7af0cfc3361.js" async></script><script type="text/javascript" src="js/main.283c39973784419c7390.js" async></script></body>
|
||||
</html>
|
||||
2
app/build/js/main.283c39973784419c7390.js
Normal file
1
app/build/js/main.283c39973784419c7390.js.map
Normal file
2
app/build/js/runtime~main.5fefa56713048225fb7f.js
Normal file
@@ -0,0 +1,2 @@
|
||||
!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.5fefa56713048225fb7f.js.map
|
||||
1
app/build/js/runtime~main.5fefa56713048225fb7f.js.map
Normal file
3
app/build/js/vendors.d617334cf7af0cfc3361.js
Normal file
89
app/build/js/vendors.d617334cf7af0cfc3361.js.LICENSE
Normal file
@@ -0,0 +1,89 @@
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
/*!
|
||||
Copyright (c) 2017 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/*! LeaderLine v1.0.5 (c) anseki https://anseki.github.io/leader-line/ */
|
||||
|
||||
/**!
|
||||
* @fileOverview Kickass library to create and place poppers near their reference elements.
|
||||
* @version 1.16.1
|
||||
* @license
|
||||
* Copyright (c) 2016 Federico Zivolo and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/** @license React v16.12.0
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.12.0
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v0.18.0
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.12.0
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
1
app/build/js/vendors.d617334cf7af0cfc3361.js.map
Normal file
18231
app/package-lock.json
generated
Normal file
149
app/package.json
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"name": "minimal-webpack-react",
|
||||
"version": "2.0.0",
|
||||
"description": "Boilerplate for react and webpack",
|
||||
"main": "index.js",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-react-constant-elements": "7.7.4",
|
||||
"@babel/plugin-transform-react-inline-elements": "7.7.4",
|
||||
"axios": "^0.19.0",
|
||||
"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.15",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"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.8.2",
|
||||
"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": "15.2.1",
|
||||
"koa-connect": "2.0.1",
|
||||
"lint-staged": "9.5.0",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"mobx": "^5.15.1",
|
||||
"node-sass": "^4.14.1",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
9
app/renovate.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"automerge": true,
|
||||
"major": {
|
||||
"automerge": false
|
||||
}
|
||||
}
|
||||
24
app/setupTests.js
Normal file
@@ -0,0 +1,24 @@
|
||||
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
Normal file
36
app/src/App.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
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 Canvas from './components/Canvas';
|
||||
|
||||
@withRouter
|
||||
@inject("store")
|
||||
@observer
|
||||
class App extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="content">
|
||||
<Route
|
||||
exact
|
||||
path={'/'}
|
||||
render={(props) =>
|
||||
<Canvas {...props}/>}
|
||||
/>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default hot(App);
|
||||
12
app/src/app.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
@import './defaults';
|
||||
|
||||
body, html, #app {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
font-family: $default-font-family;
|
||||
}
|
||||
460
app/src/components/Canvas.js
Normal file
@@ -0,0 +1,460 @@
|
||||
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 canvasStyle from "./canvas.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class Canvas extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
document.addEventListener("dragover", (e) => {
|
||||
this.store.setMousePosition(e);
|
||||
}, false);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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.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(nodeOnScreen) {
|
||||
this.store.canRelate(nodeOnScreen);
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
}
|
||||
|
||||
onClickAddGenericObjectHandler(field, o) {
|
||||
this.store.addGenericObject(field, o);
|
||||
}
|
||||
|
||||
onClickDeleteGenericObjectHandler(field, key) {
|
||||
this.store.deleteGenericObject(field, key);
|
||||
}
|
||||
|
||||
onClickResetHandler() {
|
||||
this.store.reset();
|
||||
}
|
||||
|
||||
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} />
|
||||
|
||||
<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} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
229
app/src/components/Details.js
Normal file
@@ -0,0 +1,229 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
37
app/src/components/JsonPaste.js
Normal file
@@ -0,0 +1,37 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
53
app/src/components/JsonViewer.js
Normal file
@@ -0,0 +1,53 @@
|
||||
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">
|
||||
<pre id="json-content">{JSON.stringify(this.props.json, null, 2)}</pre>
|
||||
|
||||
<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>
|
||||
)
|
||||
}
|
||||
}
|
||||
9
app/src/components/LeaderLine.js
Normal file
@@ -0,0 +1,9 @@
|
||||
let LeaderLine = require("leader-line");
|
||||
|
||||
|
||||
//module.exports = LeaderLine;
|
||||
//const singleton = new LeaderLine();
|
||||
|
||||
|
||||
|
||||
//export default LeaderLine
|
||||
88
app/src/components/Node.js
Normal file
@@ -0,0 +1,88 @@
|
||||
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(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>
|
||||
)
|
||||
}
|
||||
}
|
||||
70
app/src/components/RelationshipPicker.js
Normal file
@@ -0,0 +1,70 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
7
app/src/components/canvas.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
.canvas {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 50px;
|
||||
bottom: 50px;
|
||||
right: 50px;
|
||||
}
|
||||
93
app/src/components/details.scss
Normal file
@@ -0,0 +1,93 @@
|
||||
@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;
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
29
app/src/components/json-paste.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
.json-paste {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
|
||||
.paste-area {
|
||||
flex: 1;
|
||||
padding: 5px 0px 0px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
div {
|
||||
flex: 1;
|
||||
textarea {
|
||||
height: 97%;
|
||||
font-family: courier;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.json-controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
22
app/src/components/json-viewer.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
.json-viewer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
|
||||
pre {
|
||||
flex: 1;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
padding: 5px 0px 0px 10px;
|
||||
font-family: courier;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.json-controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
35
app/src/components/menus/Menu.js
Normal file
@@ -0,0 +1,35 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
44
app/src/components/menus/MenuItem.js
Normal file
@@ -0,0 +1,44 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
0
app/src/components/menus/ObsMenuItem.js
Normal file
35
app/src/components/menus/TopMenu.js
Normal file
@@ -0,0 +1,35 @@
|
||||
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>
|
||||
|
||||
<Tooltip />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
36
app/src/components/menus/menu.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
@import '../../defaults';
|
||||
|
||||
.menu {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 25px;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.menu-item {
|
||||
width: 40px;
|
||||
padding-right: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.obs {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 5px;
|
||||
background-color: $default-active-bg;
|
||||
|
||||
div {
|
||||
padding-top: 10px;
|
||||
padding-left: 7px;
|
||||
color: $light-font-0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
app/src/components/menus/top-menu.scss
Normal file
@@ -0,0 +1,58 @@
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
30
app/src/components/node.scss
Normal file
@@ -0,0 +1,30 @@
|
||||
@import '../defaults';
|
||||
|
||||
.node {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
transition: all .5s ease;
|
||||
|
||||
img {
|
||||
width: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-node {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show-node {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ok-border {
|
||||
border: 3px dashed $default-active-bg;
|
||||
}
|
||||
|
||||
.noway-border {
|
||||
border: 3px dashed $error-font;
|
||||
}
|
||||
52
app/src/components/relationship-picker.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
@import "../defaults";
|
||||
|
||||
.relationship-picker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
|
||||
.header {
|
||||
padding-top: 20px;
|
||||
padding-left: 20px;
|
||||
padding-bottom: 20px;
|
||||
height: 35px;
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
|
||||
.item {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
app/src/components/ui/button/Button.js
Normal file
@@ -0,0 +1,43 @@
|
||||
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
|
||||
};
|
||||
48
app/src/components/ui/button/button.scss
Normal file
@@ -0,0 +1,48 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button.def {
|
||||
width: auto;
|
||||
min-width: 130px;
|
||||
height: 30px;
|
||||
color: $light-font-0;
|
||||
font-family: $default-font-family;
|
||||
font-size: 14px;
|
||||
border-color: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
button.disabled {
|
||||
background-color: rgba(128,128,128,.8) !important;
|
||||
color: $gray-font-0 !important;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.disco-relationship {
|
||||
background: transparent;
|
||||
border: 1px solid #243244 !important;
|
||||
|
||||
}
|
||||
|
||||
.standard {
|
||||
background-color: rgba(70,160,245,.8) !important;
|
||||
}
|
||||
|
||||
.confirm {
|
||||
background-color: rgba(83,129,60,.8) !important;
|
||||
}
|
||||
|
||||
.caution {
|
||||
background-color: rgba(202,202,57,.8) !important;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
background-color: rgba(143,44,44,.8) !important;
|
||||
}
|
||||
79
app/src/components/ui/complex/ConfirmTextarea.js
Normal file
@@ -0,0 +1,79 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
125
app/src/components/ui/complex/ExternalReferences.js
Normal file
@@ -0,0 +1,125 @@
|
||||
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;
|
||||
|
||||
console.log(this.state)
|
||||
}
|
||||
|
||||
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}: {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>
|
||||
}
|
||||
115
app/src/components/ui/complex/GenericObject.js
Normal file
@@ -0,0 +1,115 @@
|
||||
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>
|
||||
}
|
||||
112
app/src/components/ui/complex/KillChain.js
Normal file
@@ -0,0 +1,112 @@
|
||||
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 killChainStyle from "./killchain.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class KillChain extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChangePhaseHandler = this.onChangePhaseHandler.bind(this);
|
||||
this.populatePhase = this.populatePhase.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onChangePhaseHandler(event) {
|
||||
const kcDomName = `kc-name-${this.props.node.id}`;
|
||||
const phaseDomName = `phase-${this.props.node.id}`;
|
||||
const kcIndex = document.getElementById(kcDomName).selectedIndex;
|
||||
const kcValue = document.getElementById(kcDomName)[kcIndex].value;
|
||||
const phaseValue = event.currentTarget.value;
|
||||
|
||||
const value = {
|
||||
kill_chain_name: kcValue,
|
||||
phase_name: phaseValue
|
||||
};
|
||||
|
||||
this.props.onChangeHandler(this.props.field, value);
|
||||
|
||||
document.getElementById(kcDomName).selectedIndex = 0;
|
||||
document.getElementById(phaseDomName).selectedIndex = 0;
|
||||
|
||||
// Reset phase name so we don't keep adding the same values
|
||||
// multiple times.
|
||||
document.getElementById(phaseDomName).innerHTML = "";
|
||||
var option = document.createElement("option");
|
||||
option.value = 0
|
||||
option.text = " -- Select Phase -- ";
|
||||
document.getElementById(phaseDomName).add(option);
|
||||
}
|
||||
|
||||
populatePhase(event) {
|
||||
const phaseDomName = `phase-${this.props.node.id}`;
|
||||
const phaseDOM = document.getElementById(phaseDomName);
|
||||
const kc = event.currentTarget.value;
|
||||
|
||||
this.props.vocab.map(item => {
|
||||
if (item.value === kc) {
|
||||
item.phases.map(phase => {
|
||||
var option = document.createElement("option");
|
||||
option.value = phase.phase_name;
|
||||
option.text = phase.label;
|
||||
phaseDOM.add(option);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
const kcName = `kc-name-${this.props.node.id}`;
|
||||
const phaseName = `phase-${this.props.node.id}`;
|
||||
|
||||
return (
|
||||
<div className="kill-chain-container">
|
||||
<div className="kill-chain-header">
|
||||
{field} <span data-tip={description} className="material-icons">info</span>
|
||||
</div>
|
||||
<div className="kill-chain-body">
|
||||
<div className="kill-chain-options">
|
||||
<select id={kcName} onChange={this.populatePhase}>
|
||||
<option value={0}> -- Select Kill Chain -- </option>
|
||||
{
|
||||
vocab.map(item => {
|
||||
return <option key={item.value}
|
||||
value={item.value}>{item.label}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
|
||||
<select id={phaseName} onChange={this.onChangePhaseHandler}>
|
||||
<option value={0}> -- Select Phase -- </option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{
|
||||
value.map((p, i) => {
|
||||
return (
|
||||
<div key={i} className="kill-chain-row">
|
||||
<div>{p.kill_chain_name} - {p.phase_name} <span onClick={() => this.props.onClickRemoveHandler(field, p)} className="material-icons">highlight_off</span></div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
94
app/src/components/ui/complex/confirmtextarea.scss
Normal file
@@ -0,0 +1,94 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
.ct-container {
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
padding-right: 10px;
|
||||
|
||||
.ct-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.ct-body {
|
||||
overflow-x: hidden;
|
||||
border: 1px solid $standard-border-color;
|
||||
min-height: 50px;
|
||||
width: 100%;
|
||||
|
||||
|
||||
.ct-block-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
|
||||
.input {
|
||||
margin: 10px 0px 10px 10px;
|
||||
flex: 1;
|
||||
|
||||
textarea {
|
||||
font-size: 14px;
|
||||
font-family: tahoma;
|
||||
}
|
||||
}
|
||||
|
||||
.add-container {
|
||||
width: 50px;
|
||||
padding: 35px 0px 0px 8px;
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
color: $default-active-bg;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ct-output {
|
||||
font-family: tahoma;
|
||||
font-size: 14px;
|
||||
padding: 0px 15px 15px 15px;
|
||||
}
|
||||
|
||||
.go-block {
|
||||
margin: 10px;
|
||||
|
||||
.go-block-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px;
|
||||
input {
|
||||
margin: 10px 0px 10px 10px;
|
||||
flex: .5;
|
||||
}
|
||||
div {
|
||||
padding: 10px;
|
||||
flex: .5;
|
||||
}
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: $error-font;
|
||||
}
|
||||
|
||||
.add {
|
||||
padding-top: 15px;
|
||||
color: $default-active-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
app/src/components/ui/complex/externalreferences.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
.er-container {
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
|
||||
.er-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.er-body {
|
||||
overflow: auto;
|
||||
border: 1px solid $standard-border-color;
|
||||
min-height: 50px;
|
||||
|
||||
.er-block {
|
||||
border: 1px solid $standard-border-color;
|
||||
margin: 10px;
|
||||
|
||||
.er-block-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
select {
|
||||
margin: 10px 0px 10px 10px;
|
||||
flex: .5;
|
||||
}
|
||||
div {
|
||||
padding: 10px;
|
||||
flex: .5;
|
||||
}
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: $error-font;
|
||||
}
|
||||
|
||||
.add {
|
||||
padding-top: 15px;
|
||||
color: $default-active-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
app/src/components/ui/complex/genericobject.scss
Normal file
@@ -0,0 +1,84 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
.go-container {
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
padding-right: 10px;
|
||||
|
||||
.go-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.go-body {
|
||||
overflow-x: hidden;
|
||||
border: 1px solid $standard-border-color;
|
||||
min-height: 50px;
|
||||
width: 100%;
|
||||
|
||||
|
||||
.go-block-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
|
||||
.input {
|
||||
margin: 10px 0px 10px 10px;
|
||||
flex: .5;
|
||||
}
|
||||
|
||||
.add-container {
|
||||
width: 50px;
|
||||
padding: 20px 0px 0px 15px;
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
color: $default-active-bg;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.go-block {
|
||||
margin: 10px;
|
||||
|
||||
.go-block-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px;
|
||||
input {
|
||||
margin: 10px 0px 10px 10px;
|
||||
flex: .5;
|
||||
}
|
||||
div {
|
||||
padding: 10px;
|
||||
flex: .5;
|
||||
}
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: $error-font;
|
||||
}
|
||||
|
||||
.add {
|
||||
padding-top: 15px;
|
||||
color: $default-active-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
app/src/components/ui/complex/killchain.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
.kill-chain-container {
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
|
||||
.kill-chain-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.kill-chain-body {
|
||||
|
||||
overflow: auto;
|
||||
border: 1px solid $standard-border-color;
|
||||
|
||||
.kill-chain-options {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
select {
|
||||
flex: .5;
|
||||
margin: 10px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.kill-chain-row {
|
||||
padding: 5px 5px 5px 10px;
|
||||
.material-icons {
|
||||
vertical-align: middle;
|
||||
color: $error-font;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
app/src/components/ui/growl/Growl.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import PropTypes from "prop-types";
|
||||
import classNames from "classnames";
|
||||
|
||||
import growlStyle from './growl.scss';
|
||||
|
||||
@observer
|
||||
export default class Growl extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onClickHideHandler() {
|
||||
if (this.props.onClickHideHandler) {
|
||||
this.props.onClickHideHandler();
|
||||
} else {
|
||||
console.warn("No JSON Viewer close handler");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onClickPanelHandler(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
render() {
|
||||
const cls = classNames({
|
||||
growl: true,
|
||||
'hide-mask': !this.props.show
|
||||
});
|
||||
|
||||
if (this.props.timer) {
|
||||
this.props.timer();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cls}>
|
||||
<div className="panel">
|
||||
{this.props.message}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
12
app/src/components/ui/growl/growl.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
@import "../../../defaults";
|
||||
|
||||
.growl {
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
z-index: $growl-index;
|
||||
background-color: $default-active-bg;
|
||||
color: $light-font-0;
|
||||
padding: 11px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
60
app/src/components/ui/inputs/ArraySelector.js
Normal file
@@ -0,0 +1,60 @@
|
||||
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 arrStyle from "./arrayselector.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class ArraySelector extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onClickHandler(field, value) {
|
||||
this.props.onClickHandler(field, value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const items = this.props.vocab ? this.props.vocab : [];
|
||||
const field = this.props.field;
|
||||
const value = this.props.value;
|
||||
const description = this.props.description;
|
||||
|
||||
let cls = classNames({
|
||||
"array-container-item": true
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="array-container">
|
||||
<div className="array-container-header">
|
||||
{field} <span data-tip={description} className="material-icons">info</span>
|
||||
</div>
|
||||
<div className="array-container-body">
|
||||
{
|
||||
items.map((item, i) => {
|
||||
if (value.indexOf(item) > -1) {
|
||||
cls = classNames({
|
||||
"array-container-item": true,
|
||||
"array-container-selected": true
|
||||
});
|
||||
} else {
|
||||
cls = classNames({
|
||||
"array-container-item": true
|
||||
});
|
||||
}
|
||||
return <div className={cls} key={i} onClick={() => this.onClickHandler(field, item)}>{item}</div>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
51
app/src/components/ui/inputs/Boolean.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {observer, inject} from "mobx-react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import csvStyle from "./boolean.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class ArraySelector extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onClickHandler(field, value) {
|
||||
this.props.onClickHandler(field, value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const value = this.props.selected;
|
||||
let trueCls = classNames({
|
||||
selected: false
|
||||
});
|
||||
|
||||
let falseCls = classNames({
|
||||
selected: false
|
||||
});
|
||||
|
||||
if (value) {
|
||||
trueCls = classNames({
|
||||
selected: true
|
||||
});
|
||||
} else {
|
||||
falseCls = classNames({
|
||||
selected: true
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="boolean">
|
||||
<div className={trueCls} onClick={() => this.props.onClick(this.props.name, true)}>True</div>
|
||||
<div className={falseCls} onClick={() => this.props.onClick(this.props.name, false)}>False</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
36
app/src/components/ui/inputs/CSVInput.js
Normal file
@@ -0,0 +1,36 @@
|
||||
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 "./Text";
|
||||
|
||||
import csvStyle from "./csvselector.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class ArraySelector extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onClickHandler(field, value) {
|
||||
this.props.onClickHandler(field, value);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const value = this.props.value.join();
|
||||
|
||||
return (
|
||||
<Text name={this.props.name}
|
||||
value={value}
|
||||
onChange={this.props.onChangeHandler} />
|
||||
)
|
||||
}
|
||||
}
|
||||
33
app/src/components/ui/inputs/DateTime.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import {observer, inject} from 'mobx-react';
|
||||
import moment from 'moment';
|
||||
import DatePicker from 'react-datepicker';
|
||||
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dateStyle from './datetime.scss';
|
||||
|
||||
export default class DateTime extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
onChange(datetime) {
|
||||
this.props.onChange(this.props.name, datetime);
|
||||
}
|
||||
|
||||
render() {
|
||||
let dts = this.props.selected;
|
||||
|
||||
if (typeof dts === 'string') {
|
||||
let dateObj = new Date(dts);
|
||||
dts = dateObj;
|
||||
}
|
||||
|
||||
return (
|
||||
<DatePicker selected={dts} onChange={this.onChange} name={this.props.name} />
|
||||
)
|
||||
}
|
||||
}
|
||||
32
app/src/components/ui/inputs/Slider.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { toJS } from "mobx";
|
||||
|
||||
import RCSlider from "rc-slider/lib/Slider";
|
||||
import 'rc-slider/assets/index.css'
|
||||
|
||||
import textStyle from "./slider.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class Slider extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChangeSliderHandler = this.onChangeSliderHandler.bind(this);
|
||||
}
|
||||
|
||||
onChangeSliderHandler(value) {
|
||||
this.props.onChangeHandler(this.props.field, value);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<RCSlider className="horizontal-slider"
|
||||
value={this.props.value}
|
||||
marks={{ 10: 10, 20: 20, 30: 30, 40: 40, 50: 50, 60: 60, 70: 70, 80: 80, 90: 90, 100: 100 }}
|
||||
onChange={this.onChangeSliderHandler} />
|
||||
)
|
||||
}
|
||||
}
|
||||
68
app/src/components/ui/inputs/Text.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {observer, inject} from "mobx-react";
|
||||
|
||||
import textStyle from "./text.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class Text extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onChangeHandler = this.onChangeHandler.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.hasInitialFocus) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.input) {
|
||||
this.input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDownHandler(event) {
|
||||
if (event.keyCode === 13 && this.props.onReturn) {
|
||||
this.props.onReturn();
|
||||
} else if (event.keyCode === 27 && this.props.onEscape) {
|
||||
this.props.onEscape();
|
||||
}
|
||||
}
|
||||
|
||||
onChangeHandler(event) {
|
||||
this.props.onChange(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
let inputType = this.props.type ? this.props.type : 'text';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
name={this.props.name}
|
||||
type={inputType}
|
||||
ref={c => { this.input = c }}
|
||||
autoComplete={this.props.autocomplete || "off"}
|
||||
className="def"
|
||||
placeholder={this.props.placeholder}
|
||||
onChange={this.onChangeHandler}
|
||||
onKeyDown={e => this.onKeyDownHandler(e)}
|
||||
value={this.props.value}
|
||||
disabled={this.props.disabled}
|
||||
id={this.props.id}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text.propTypes = {
|
||||
hasInitialFocus: PropTypes.bool,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onReturn: PropTypes.func,
|
||||
onEscape: PropTypes.func
|
||||
};
|
||||
59
app/src/components/ui/inputs/TextArea.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from "react";
|
||||
import {observer, inject} from "mobx-react";
|
||||
|
||||
import textStyle from "./text.scss";
|
||||
|
||||
@inject("store")
|
||||
@observer
|
||||
export default class TextArea extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onChangeHandler = this.onChangeHandler.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.hasInitialFocus) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.input) {
|
||||
this.input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDownHandler(event) {
|
||||
if (event.keyCode === 13 && this.props.onReturn) {
|
||||
this.props.onReturn();
|
||||
} else if (event.keyCode === 27 && this.props.onEscape) {
|
||||
this.props.onEscape();
|
||||
}
|
||||
}
|
||||
|
||||
onChangeHandler(event) {
|
||||
this.props.onChange(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
let rows = this.props.rows ? this.props.rows : 1;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<textarea
|
||||
name={this.props.name}
|
||||
ref={c => { this.input = c }}
|
||||
autoComplete={this.props.autocomplete || "off"}
|
||||
className="def"
|
||||
placeholder={this.props.placeholder}
|
||||
onChange={this.onChangeHandler}
|
||||
onKeyDown={e => this.onKeyDownHandler(e)}
|
||||
value={this.props.value}
|
||||
disabled={this.props.disabled}
|
||||
id={this.props.id}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
38
app/src/components/ui/inputs/arrayselector.scss
Normal file
@@ -0,0 +1,38 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
.array-container {
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
|
||||
.array-container-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.array-container-body {
|
||||
height: 100px;
|
||||
overflow: auto;
|
||||
border: 1px solid $standard-border-color;
|
||||
|
||||
.array-container-item {
|
||||
cursor: pointer;
|
||||
line-height: 30px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.array-container-selected {
|
||||
background-color: $default-active-bg;
|
||||
}
|
||||
|
||||
.array-container-item:hover {
|
||||
background-color: $light-font-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
app/src/components/ui/inputs/boolean.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
.boolean {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
line-height: 30px;
|
||||
border: 1px solid $standard-border-color;
|
||||
width: 99%;
|
||||
|
||||
div {
|
||||
flex: .5;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: $default-active-bg;
|
||||
}
|
||||
|
||||
}
|
||||
0
app/src/components/ui/inputs/csvselector.scss
Normal file
17
app/src/components/ui/inputs/datetime.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
.react-datepicker-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-datepicker__input-container input {
|
||||
height: 39px;
|
||||
border: 1px solid $standard-border-color;
|
||||
background-color: transparent;
|
||||
width: 97%;
|
||||
padding-left: 10px;
|
||||
font-family: $default-font-family;
|
||||
color: #000;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
5
app/src/components/ui/inputs/slider.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
.rc-slider-track {
|
||||
background-color: transparet;
|
||||
}
|
||||
40
app/src/components/ui/inputs/text.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
input:focus, textarea:focus, select {
|
||||
outline:none;
|
||||
}
|
||||
|
||||
input.def, textarea, select {
|
||||
height: 39px;
|
||||
border: 1px solid $standard-border-color;
|
||||
background-color: transparent;
|
||||
width: 97%;
|
||||
padding-left: 10px;
|
||||
color: $dark-font-0;
|
||||
font-size: 16px;
|
||||
font-family: $default-font-family;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
select {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: #4f5257;
|
||||
}
|
||||
|
||||
::-ms-input-placeholder {
|
||||
color: red;
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: red;
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: red;
|
||||
}
|
||||
44
app/src/components/ui/panel/Panel.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import PropTypes from "prop-types";
|
||||
import classNames from "classnames";
|
||||
|
||||
import panelStyle from './panel.scss';
|
||||
|
||||
@observer
|
||||
export default class Panel extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onClickHideHandler = this.onClickHideHandler.bind(this);
|
||||
}
|
||||
|
||||
onClickHideHandler() {
|
||||
if (this.props.onClickHideHandler) {
|
||||
this.props.onClickHideHandler();
|
||||
} else {
|
||||
console.warn("No JSON Viewer close handler");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onClickPanelHandler(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
render() {
|
||||
const cls = classNames({
|
||||
mask: true,
|
||||
'hide-mask': !this.props.show
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={cls} onClick={this.onClickHideHandler}>
|
||||
<div className="panel" onClick={this.onClickPanelHandler}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
28
app/src/components/ui/panel/panel.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
@import '../../../defaults';
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
background-color: rgba(0, 0, 0,.2);
|
||||
z-index: $panel-mask-index;
|
||||
|
||||
.panel {
|
||||
position: absolute;
|
||||
width: 600px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
background-color: #fff;
|
||||
box-shadow: -20px 25px 50px 0px #000;
|
||||
z-index: $panel-index;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-mask {
|
||||
display: none;
|
||||
}
|
||||