Skip to content
Snippets Groups Projects
Commit 8754a5e7 authored by Samuel GAIST's avatar Samuel GAIST
Browse files

[conda] Remove all js files

parent 58615541
No related branches found
No related tags found
No related merge requests found
Showing
with 0 additions and 18830 deletions
{
"plugins": [
"@babel/plugin-syntax-flow",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-class-properties"
],
"presets": [
"@babel/preset-react",
[
"@babel/preset-env",
{
"modules": false,
"debug": true,
"targets": {
"browsers": [
"last 1 Firefox versions",
"last 1 Chrome versions"
]
}
}
],
"@babel/preset-flow"
],
"env": {
"test": {
"plugins": [
]
}
},
"ignore": [
"node_modules"
]
}
module.exports = {
env: {
'browser': true,
'worker': true,
},
parserOptions: {
'parser': 'babel-eslint',
'ecmaVersion': 8,
'sourceType': 'module',
'modules': true,
'ecmaFeatures': {
'jsx': true,
'impliedStrict': true,
'experimentalObjectRestSpread': true,
},
},
extends: [
'plugin:react/recommended',
'plugin:flowtype/recommended',
],
plugins: [
// lints for browser support (APIs and such)
// uses browserslist tech
'compat',
// react
'react',
// flow type annotations
'flowtype',
],
rules: {
// tabs not spaces
'indent': [
'error',
'tab',
// indent for switch statements
{
'SwitchCase': 1,
'MemberExpression': 0
}
],
// we want tabs
'no-tabs': 0,
// only unix endings
'linebreak-style': ['error', 'unix'],
// single quotes & double quotes are the same,
// but encourage single quotes cuz its one less
// button press
'quotes': ['error', 'single', { 'allowTemplateLiterals': true }],
// always use semi-colons at end of statements
'semi': ['error', 'always'],
// errors from compat are real errors
'compat/compat': 2,
// always have spaces in template strings curly braces
'template-curly-spacing': ['error', 'always'],
}
};
[ignore]
<PROJECT_ROOT>/node_modules/
.*\.spec\.jsx
[include]
<PROJECT_ROOT>/node_modules/react-dom/
<PROJECT_ROOT>/node_modules/react-router-dom/
<PROJECT_ROOT>/node_modules/reactstrap/
<PROJECT_ROOT>/src/*
[libs]
<PROJECT_ROOT>/flow-typed/
[lints]
[options]
module.name_mapper='^@helpers' ->'<PROJECT_ROOT>/src/helpers'
module.name_mapper='^@store' ->'<PROJECT_ROOT>/src/store'
module.name_mapper='^@test' ->'<PROJECT_ROOT>/test'
{
"plugins": {
"postcss-smart-import": {},
"postcss-cssnext": {}
}
}
{
"extends": "stylelint-config-standard",
"rules": {
"indentation": "tab"
}
}
{
"ecmaVersion": 6,
"libs": [
"browser",
"react"
],
"plugins": {
"jsx": {},
"modules": {},
"es_modules": {},
"webpack": {
"configPath": "./webpack.config.js"
}
},
"loadEagerly": [
"./src/**/*.js",
"./src/**/*.jsx"
]
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
// we can just use the exact same webpack config by requiring it
// however, remember to delete the original entry since we don't
// need it during tests
let webpackConfig = require('./webpack.config.js');
delete webpackConfig.entry;
// karma.conf.js
module.exports = function (config) {
config.set({
browsers: [
'FirefoxHeadless',
//'ChromiumHeadless',
],
customLaunchers: {
FirefoxHeadless: {
base: 'Firefox',
flags: ['-headless']
}
},
frameworks: ['mocha'],
// this is the entry file for all our tests.
files: [
'./test/index.js',
],
// we will pass the entry file to webpack for bundling.
preprocessors: {
'**/*': [ 'webpack', 'sourcemap' ],
},
reporters: [
'mocha',
'coverage'
],
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'html', subdir: 'report-html' },
{ type: 'lcov', subdir: 'report-lcov' },
{ type: 'cobertura', subdir: '.', file: 'cobertura.txt' },
{ type: 'lcovonly', subdir: '.', file: 'report-lcovonly.txt' },
{ type: 'teamcity', subdir: '.', file: 'teamcity.txt' },
{ type: 'text', subdir: '.', file: 'text.txt' },
{ type: 'text-summary', subdir: '.', file: 'text-summary.txt' },
]
},
// use the webpack config
webpack: webpackConfig,
// avoid walls of useless text
webpackMiddleware: {
stats: 'errors-only'
},
singleRun: true,
});
};
pre {
white-space: pre-wrap;
}
.preInline {
display: inline;
}
/* react-contextmenu styling
* taken from react-contextmenu's example CSS https://github.com/vkbansal/react-contextmenu/blob/65df2dec9737eed4e5e1b0b8fbc1018629082f89/examples/react-contextmenu.css
* adapted to more closely conform to this repo's stylelint's settings (see .stylelintrc)
*/
.react-contextmenu {
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 16px;
color: #373a3c;
text-align: left;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.25rem;
outline: none;
opacity: 0;
pointer-events: none;
transition: opacity 250ms ease;
}
.react-contextmenu.react-contextmenu--visible {
opacity: 1;
pointer-events: auto;
}
.react-contextmenu-item {
padding: 3px 20px;
font-weight: 400;
line-height: 1.5;
color: #373a3c;
text-align: inherit;
white-space: nowrap;
background: 0 0;
border: 0;
cursor: pointer;
}
.react-contextmenu-item.react-contextmenu-item--active,
.react-contextmenu-item.react-contextmenu-item--selected {
color: #fff;
background-color: #20a0ff;
border-color: #20a0ff;
text-decoration: none;
}
.react-contextmenu-item.react-contextmenu-item--disabled,
.react-contextmenu-item.react-contextmenu-item--disabled:hover {
color: #878a8c;
background-color: transparent;
border-color: rgba(0, 0, 0, 0.15);
}
.react-contextmenu-item--divider {
margin-bottom: 3px;
padding: 2px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.15);
cursor: inherit;
}
.react-contextmenu-item--divider:hover {
background-color: transparent;
border-color: rgba(0, 0, 0, 0.15);
}
.react-contextmenu-item.react-contextmenu-submenu {
padding: 0;
}
.react-contextmenu-item.react-contextmenu-submenu > .react-contextmenu-item {
}
.react-contextmenu-item.react-contextmenu-submenu > .react-contextmenu-item::after {
content: "▶";
display: inline-block;
position: absolute;
right: 7px;
}
.example-multiple-targets::after {
content: attr(data-count);
display: block;
}
// bootstrap first
import './main.css';
import 'bootstrap/dist/css/bootstrap.min.css';
// rest of app
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './src/components/App.jsx';
import store from '@store';
ReactDOM.render(
<Provider store={ store }>
<App/>
</Provider>
,
document.getElementById('root')
);
This diff is collapsed.
{
"name": "beat.editor",
"version": "0.0.0",
"description": "A local editor for BEAT objects, including a graphical toolchain editor and a simple web server.",
"main": "index.js",
"scripts": {
"start": "cross-env NODE_ENV=debug webpack-dev-server --hot",
"prebuild": "rimraf ../../beat/editor/js",
"build": "cross-env NODE_ENV=production webpack",
"test-start": "cross-env BABEL_ENV=test NODE_ENV=test karma start --no-single-run",
"test": "cross-env BABEL_ENV=test NODE_ENV=test karma start",
"lint": "eslint --ext .js,.jsx src && eslint test && stylelint 'src/**/*.jsx, src/**/*.css, src/**/*.html' && npm run flow",
"lint:fix": "eslint --ext .js,.jsx src --fix && eslint test --fix",
"flow": "flow",
"prepush": "npm test"
},
"browserslist": [
"last 2 Chrome versions",
"last 2 Firefox versions",
"last 1 Safari versions"
],
"author": "",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.1.0",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-syntax-flow": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"babel-eslint": "^9.0.0",
"babel-loader": "^8.0.2",
"chai": "^4.1.2",
"chai-enzyme": "^1.0.0-beta.1",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"deep-equal-in-any-order": "^1.0.10",
"enzyme": "^3.6.0",
"enzyme-adapter-react-16": "^1.5.0",
"eslint": "^5.6.0",
"eslint-plugin-compat": "^2.5.1",
"eslint-plugin-flowtype": "^2.50.1",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-react": "^7.11.1",
"flow-bin": "^0.81.0",
"html-webpack-plugin": "^3.2.0",
"husky": "^0.14.3",
"karma": "^3.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage": "^1.1.2",
"karma-firefox-launcher": "^1.1.0",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^3.0.5",
"mocha": "^5.2.0",
"postcss": "^7.0.2",
"postcss-cli": "^6.0.0",
"postcss-cssnext": "^3.1.0",
"postcss-loader": "^3.0.0",
"postcss-smart-import": "^0.7.6",
"react-hot-loader": "^4.3.8",
"react-popper": "^1.0.2",
"react-test-renderer": "^16.5.2",
"redux-devtools": "^3.4.1",
"rimraf": "^2.6.2",
"selenium-webdriver": "^4.0.0-alpha.1",
"sinon": "^6.3.4",
"style-loader": "^0.23.0",
"stylelint": "^9.5.0",
"stylelint-config-standard": "^18.2.0",
"svg-inline-loader": "^0.8.0",
"tern-jsx": "^1.0.3",
"terser-webpack-plugin": "^1.1.0",
"webpack": "^4.19.1",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.8",
"webpack-visualizer-plugin": "^0.1.11",
"worker-loader": "^2.0.0"
},
"dependencies": {
"ajv": "^6.5.3",
"bootstrap": "^4.1.3",
"classnames": "^2.2.6",
"d3": "^5.7.0",
"fast-copy": "^1.2.2",
"fast-levenshtein": "^2.0.6",
"fuse.js": "^3.2.1",
"prop-types": "^15.6.2",
"react": "^16.5.2",
"react-contextmenu": "^2.9.3",
"react-dom": "^16.5.2",
"react-redux": "^5.0.7",
"react-router-dom": "^4.3.1",
"react-transition-group": "^2.4.0",
"reactstrap": "^6.4.0",
"redux": "^4.0.0",
"redux-thunk": "^2.3.0",
"reselect": "^3.0.1",
"throttle-debounce": "^2.0.1"
}
}
// @flow
import React from 'react';
import {
Container,
Row,
Col,
} from 'reactstrap';
import {
HashRouter as Router,
Route,
Switch,
} from 'react-router-dom';
import { hot } from 'react-hot-loader';
import MainNav from './MainNav.jsx';
import MainContent from './MainContent.jsx';
import HomeContent from './HomeContent.jsx';
type Props = {
};
type State = {
};
class App extends React.PureComponent<Props, State> {
constructor(props: Props){
super(props);
}
render () {
return (
<Router>
<Container>
<Route component={MainNav} />
<Switch>
<Route path='/:entity' component={MainContent} />
<Route>
<HomeContent />
</Route>
</Switch>
</Container>
</Router>
);
}
}
export default hot(module)(App);
// @flow
import * as React from 'react';
import {
Input,
Alert,
InputGroup,
InputGroupAddon,
UncontrolledTooltip,
Tooltip,
} from 'reactstrap';
import { rxField } from '@helpers/beat';
type Props = {
// consumer-supplied func for validating input
// if the func finds the input isnt valid, it should return a react.node of text for the error message
validateFunc?: (string) => (true | React.Node),
// if the current input in the CacheInput instance is valid, this func will be called with that value
onChange: (any) => any,
// the current value for this cacheinput
value: string,
// for edge cases
children?: React.Node,
// many fields in the beat editor are restricted to a certain "slugified" format
// if this cacheinput needs that, use this flag
fieldTest?: boolean,
// update delay in ms, defaults to 500
// basically just simple throttling to keep from overwhelming complex updates in the parent
delay?: number,
// danger color highlight
invalid?: boolean,
};
type State = {
// the current string in the CacheInput input field,
// which may or may not be valid
cache: string,
// whether the cache is valid
valid: boolean,
// a text-like react node that represents an error message
invalidityText?: React.Node,
// the delay to use (default 500)
msDelay: number,
};
// test for the "fieldTest" flag, with a nice error message
const fieldTestResults = (input: string): true | React.Node => {
return (rxField.test(input) ||
<span>
The field name must be at least one character and consisting of only of alphanumeric ASCII characters, `_`, and `-`.
</span>
);
};
// top-level validity func with some edgecase checking
// validateFunc arg lets you also check using a user-defined validate func
const isValid = (input: string, currVal: string, validateFunc: (string) => true | React.Node, fieldTest: boolean): true | React.Node => {
if(input === currVal)
return true;
if(fieldTest){
const res = fieldTestResults(input);
if(res !== true)
return res;
}
const funcRes = validateFunc(input);
return funcRes;
};
// Provides a bootstrap-styled wrapper around <input /> that caches the raw user input until a valid input is provided.
// Also throttles the onChange event to prevent many parent state updates in a short time
class CacheInput extends React.Component<Props, State>{
constructor(props: Props){
super(props);
}
state = {
cache: this.props.value,
valid: false,
invalidityText: undefined,
// default of 500ms delay
msDelay: this.props.delay !== undefined ? this.props.delay : 500,
};
// check validation as soon as it mounts
componentDidMount () {
this.updateValidity(this.state.cache);
}
// if the parent's model (the props) changes, reset input
componentDidUpdate = (prevProps: Props) => {
if(this.props.value !== prevProps.value){
this.setState({
cache: (this.props.value),
});
this.clearTimer();
}
}
// gets the validity info of an input string given a curr val
updateValidity = (input: string, currVal?: string = this.props.value) => {
const func = this.props.validateFunc || (() => true);
const validRes = isValid(input, currVal, func, this.props.fieldTest || false);
this.setState({
valid: validRes === true,
invalidityText: validRes !== true ? validRes : undefined
});
return validRes === true;
}
// the user can pass any props they want onto the inner <Input /> element
// some props shouldn't be passed though
getInputProps = () => {
const inputProps = { ...this.props };
delete inputProps['validateFunc'];
delete inputProps['children'];
delete inputProps['fieldTest'];
delete inputProps['delay'];
return inputProps;
}
// throttle timer ref
onChangeTimer = null;
// clears the throttle timer
clearTimer = () => {
if(this.onChangeTimer)
clearTimeout(this.onChangeTimer);
}
// called whenever the user changes the raw input with the raw input event
// for perf reasons it really focuses on returning when possible
change = (e: any) => {
this.clearTimer();
const newInput = e.target.value;
// no need to update the state if the value is the same
if(newInput === this.state.cache)
return;
const newValid = this.updateValidity(newInput);
this.setState({
cache: newInput,
});
// no need to update props if value is the same
if(newInput === this.props.value)
return;
if(newValid && this.props.onChange){
if(this.state.msDelay === 0){
this.props.onChange(e);
} else {
// by default in react events have a "persist" method
// that lets you keep it around for a bit longer than normal
// in testing environments the event persists by default
if(e.persist)
e.persist();
this.onChangeTimer = setTimeout(() => {
this.props.onChange(e);
}, this.state.msDelay);
}
}
}
// the Tooltips are annoying in test environments and need a react ref
target = null
render () {
// either if invalid or if the consuming component set it
const dangerColor = !this.state.valid || this.props.invalid;
return (
<InputGroup>
{ this.props.children }
<Input
type='text'
{...this.getInputProps()}
value={this.state.cache}
onChange={this.change}
valid={!dangerColor}
invalid={dangerColor}
/>
{ !this.state.valid &&
<div ref={target => {this.target = target;}} className='input-group-append'>
<span className='input-group-text'>
<strong className='text-danger'>!</strong>
</span>
</div>
}
{ !this.state.valid &&
<UncontrolledTooltip target={() => this.target}>
{ this.state.invalidityText }
</UncontrolledTooltip>
}
</InputGroup>
);
}
}
export default CacheInput;
// @flow
import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { spies } from '@test';
import C from './CacheInput.jsx';
describe('<CacheInput />', () => {
let wrapper;
const validateFunc = sinon.spy();
const onChange = sinon.spy();
const fieldTest = true;
const children = <span>child</span>;
const value = 'value';
afterEach(() => {
if(wrapper && wrapper.unmount)
wrapper.unmount();
});
it(`has 2 required props`, () => {
wrapper = mount(
<C
onChange={onChange}
value={value}
/>
);
expect(wrapper).to.have.props(
['onChange', 'value']
).deep.equal(
[onChange, value]
);
expect(wrapper).to.not.have.props(
['validateFunc', 'fieldTest', 'children']
);
});
it(`can take up to 5 props`, () => {
const validateFunc = sinon.spy();
const onChange = sinon.spy();
const fieldTest = true;
const children = <span>child</span>;
const value = 'value';
wrapper = mount(
<C
validateFunc={validateFunc}
onChange={onChange}
fieldTest={fieldTest}
value={value}
>{ children }</C>
);
expect(wrapper).to.have.props(
['validateFunc', 'onChange', 'fieldTest', 'value', 'children']
).deep.equal(
[validateFunc, onChange, fieldTest, value, children]
);
});
});
//@flow
import * as React from 'react';
import {
InputGroupAddon,
Button,
} from 'reactstrap';
type Props = {
deleteFunc: (any) => any,
}
// super simple wrapper for a button that attaches to an input
// styled like a Delete button
const DeleteInputBtn = ({ deleteFunc }: Props) => (
<InputGroupAddon addonType='prepend'>
<Button
color='danger'
onClick={(e) => deleteFunc(e)}
>
X
</Button>
</InputGroupAddon>
);
export default DeleteInputBtn;
// @flow
import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { spies } from '../../test';
import C from './DeleteInputBtn.jsx';
describe('<DeleteInputBtn />', () => {
let wrapper;
afterEach(() => {
if(wrapper && wrapper.unmount)
wrapper.unmount();
});
it('takes a "deleteFunc" prop', () => {
const deleteFunc = sinon.spy();
wrapper = mount(<C deleteFunc={deleteFunc} />);
expect(wrapper).to.have.prop('deleteFunc', deleteFunc);
});
it('calls the deleteFunc when clicked', () => {
const deleteFunc = sinon.spy();
wrapper = mount(<C deleteFunc={deleteFunc} />);
wrapper.find('button').simulate('click');
expect(deleteFunc.calledOnce).to.equal(true);
});
});
// @flow
import * as React from 'react';
import {
Container,
Row,
Col,
TabContent,
TabPane,
Nav,
NavItem,
NavLink,
Input,
InputGroupAddon,
InputGroup,
} from 'reactstrap';
import { connect } from 'react-redux';
import cn from 'classnames';
import * as Actions from '@store/actions.js';
import * as Selectors from '@store/selectors.js';
import {
Route,
Link
} from 'react-router-dom';
import type { BeatObject, BeatEntity } from '@helpers/beat';
import { getDefaultEntityObject, pluralize } from '@helpers/beat';
import { genModuleApiFuncs } from '@helpers/api';
import ValidSchemaBadge from './ValidSchemaBadge.jsx';
import DataformatEditorContainer from './dataformat';
import AlgorithmEditorContainer from './algorithm';
import LibraryEditorContainer from './library';
import DatabaseEditorContainer from './database';
import ExperimentEditorContainer from './experiment';
import ToolchainEditorContainer from './toolchain';
import PlotterEditorContainer from './plotter';
import PlotterparameterEditorContainer from './plotterparameter';
type Props = {
// the match object from react-router
// used to find which object to render
match: any,
// the history object from react-router
// used to push new history to
history: any,
// gets the object based on the current URL
getEntityObject: () => BeatObject,
// gets the object's index # in the Redux array based on the current URL
getEntityIndex: () => number,
// updates the current object
updateFunc: (BeatObject) => any,
// the current BEAT entity being show
entity: BeatEntity,
// the absolute path to the user's prefix folder
prefix: string,
};
// 3 tabs so far: editor, docs, raw json
type Tab = '0' | '1' | '2';
type State = {
// which tab is active
activeTab: Tab,
};
// wrapper for editors
// doesnt really do much now besides the raw json tab
export class EntityDetail extends React.Component<Props, State> {
constructor(props: Props){
super(props);
}
state = {
activeTab: '0',
}
switchToTab = (tab: Tab) => {
this.setState({
activeTab: tab
});
}
saveChanges = (newObj: BeatObject) => {
this.props.updateFunc(newObj);
const put = genModuleApiFuncs(this.props.entity).put;
return put([newObj]);
}
render () {
const name = this.props.match.params.name;
const obj = this.props.getEntityObject();
const index = this.props.getEntityIndex();
let expName;
if(this.props.entity === 'experiment'){
const segs = name.split('/');
const usern = segs.shift();
const expn = segs.pop();
const tcn = segs.join('/');
expName = <span>{ usern }/<Link to={`/toolchain/${ tcn }`}>{ tcn }</Link>/{ expn }</span>
}
return (
<Container>
<Row className='mb-1'>
<Col>
{/* title line (name & validation info) */}
<h4 className='text-center'>
<span style={{'textTransform': 'capitalize'}}>
{ this.props.entity }
</span>
{' '}
<pre style={{display: 'inline'}}>
{ expName || name }
</pre>
<ValidSchemaBadge entity={this.props.entity} obj={obj} />
</h4>
{/* path line */}
<InputGroup>
<InputGroupAddon addonType='prepend'>Path:</InputGroupAddon>
<Input
readOnly
width='80'
value={`${ this.props.prefix }/${ pluralize(this.props.entity) }/${ name }`}
/>
</InputGroup>
</Col>
</Row>
<Row>
<Col sm='12'>
<Nav tabs className='nav-fill'>
<NavItem>
<NavLink
className={cn({ active: this.state.activeTab === '0' })}
onClick={() => this.switchToTab('0')}
>
Editor
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={cn({ active: this.state.activeTab === '1' })}
onClick={() => this.switchToTab('1')}
>
Raw JSON
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={this.state.activeTab} className='mt-2'>
{/* Uses the appropriate editor, could probably be done nicer */}
<TabPane tabId='0'>
{
this.props.entity === 'algorithm' &&
<AlgorithmEditorContainer
saveFunc={this.saveChanges}
index={index}
/>
}
{
this.props.entity === 'dataformat' &&
<DataformatEditorContainer
saveFunc={this.saveChanges}
index={index}
/>
}
{
this.props.entity === 'library' &&
<LibraryEditorContainer
saveFunc={this.saveChanges}
index={index}
/>
}
{
this.props.entity === 'database' &&
<DatabaseEditorContainer
saveFunc={this.saveChanges}
index={index}
/>
}
{
this.props.entity === 'experiment' &&
<ExperimentEditorContainer
saveFunc={this.saveChanges}
index={index}
/>
}
{
this.props.entity === 'toolchain' &&
<ToolchainEditorContainer
saveFunc={this.saveChanges}
index={index}
/>
}
{
this.props.entity === 'plotter' &&
<PlotterEditorContainer
saveFunc={this.saveChanges}
index={index}
/>
}
{
this.props.entity === 'plotterparameter' &&
<PlotterparameterEditorContainer
saveFunc={this.saveChanges}
index={index}
/>
}
</TabPane>
<TabPane tabId='1'>
<pre>{ JSON.stringify(obj, null, 4) }</pre>
</TabPane>
</TabContent>
</Col>
</Row>
</Container>
);
}
}
const mapStateToProps = (state, ownProps) => {
// which beat entity is currently active: 'algorithm', 'plotter', 'database', etc.
const entity = ownProps.match.params.entity;
// the name of the object ("atnt/1", "plot/isoroc/1", "user/tc/test/1/exp", etc.)
const name = ownProps.match.params.name;
const obj = {
// uses a selector based off the entity and finds the obj given the name
// if the obj doesnt exist (huge edge case) just return a default obj to not break everything
getEntityObject: (): BeatObject => Selectors[`${ entity }Get`](state).find(o => o.name === name) || getDefaultEntityObject(),
getEntityIndex: (): number => Selectors[`${ entity }Get`](state).findIndex(o => o.name === name),
entity,
prefix: Selectors.settingsGet(state).prefix,
};
return obj;
};
const mapDispatchToProps = (dispatch, ownProps) => ({
// replace the obj in the Redux store with the new object
updateFunc: (obj) => {
dispatch(Actions[`${ ownProps.match.params.entity }Update`](ownProps.match.params.name, obj));
ownProps.history.push(`/${ ownProps.match.params.entity }/${ obj.name }`);
},
});
export default connect(mapStateToProps, mapDispatchToProps)(EntityDetail);
// @flow
import React from 'react';
import {
Container,
Row,
Col,
ListGroup,
ListGroupItem,
Button,
ButtonGroup,
Card,
CardHeader,
CardBody,
} from 'reactstrap';
import {
Switch,
Route,
} from 'react-router-dom';
import type { BeatEntity } from '@helpers/beat.js';
import { pluralize } from '@helpers/beat.js';
import EntityListContainer from './EntityList.jsx';
import NewEntityModal from './NewEntityModal.jsx';
import EntityDetailContainer from './EntityDetail.jsx';
import SearchBar from './SearchBar.jsx';
type Props = {
entity: BeatEntity,
};
type State = {
newOpen: boolean,
};
class EntityHome extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
}
state = {
newOpen: false,
}
toggleNew = () => this.setState({ newOpen: !this.state.newOpen });
render () {
return (
<Switch>
<Route exact path={'/:entity/:name+'} component={EntityDetailContainer} />
<Route>
<Container>
<Row className='mb-3'>
<Col>
<div className='d-flex'>
<h3>{ pluralize(this.props.entity) }</h3>
<SearchBar
entity={this.props.entity}
/>
<div className='ml-auto'>
<Button outline color='success' onClick={this.toggleNew}>New</Button>
<NewEntityModal
toggle={this.toggleNew}
isOpen={this.state.newOpen}
entity={this.props.entity}
nameOrVersion={true}
/>
</div>
</div>
</Col>
</Row>
<Row>
<Col>
<EntityListContainer entity={this.props.entity} />
</Col>
</Row>
</Container>
</Route>
</Switch>
);
}
}
export default EntityHome;
// @flow
import * as React from 'react';
import {
Container,
Row,
Col,
ListGroup,
ListGroupItem,
Button,
ButtonGroup,
Card,
CardHeader,
CardBody,
Badge,
Modal, ModalHeader, ModalBody, ModalFooter,
} from 'reactstrap';
import {
Route,
Link
} from 'react-router-dom';
import { connect } from 'react-redux';
import * as Selectors from '@store/selectors';
import * as Actions from '@store/actions';
import { BEAT_ENTITIES, getTcFromExpName } from '@helpers/beat';
import type { BeatEntity, BeatObject } from '@helpers/beat';
import ValidSchemaBadge from './ValidSchemaBadge.jsx';
import NewEntityModal from './NewEntityModal.jsx';
type ElProps = {
data: BeatObject,
entity: BeatEntity,
copyFunc: () => any,
deleteFunc: () => any,
versionFunc?: () => any,
};
const El = ({ data, entity, copyFunc, deleteFunc, versionFunc }: ElProps) => (
<ListGroupItem className='d-flex align-items-center'>
<span>
<Link to={`/${ entity }/${ data.name }`}>
{ data.name }
</Link>
<ValidSchemaBadge entity={entity} obj={data} />
<small> { data.contents['description'] || data.contents['#description'] }</small>
</span>
<div className='ml-auto'>
{ versionFunc && <Button outline color='success' onClick={() => versionFunc()}>New Version</Button> }{' '}
<Button outline color='success' onClick={() => copyFunc()}>Clone</Button>{' '}
<Button outline color='danger' onClick={() => deleteFunc()}>Delete</Button>
</div>
</ListGroupItem>
);
type Props = {
entity: BeatEntity,
data: BeatObject[],
// deleting toolchains should show the exps that will be deleted in the popup
experiments: BeatObject[],
deleteFunc: (BeatObject, ?BeatEntity) => () => any,
};
type State = {
newModalOpen: boolean,
copyObj: {},
deleteModalData: false | BeatObject,
nameOrVersion: boolean,
};
export class EntityList extends React.Component<Props, State> {
constructor (props: Props) {
super(props);
}
state = {
newModalOpen: false,
copyObj: {},
deleteModalData: false,
nameOrVersion: false,
}
toggleDeleteModal = (e: false | BeatObject = false) => this.setState({ deleteModalData: e || false });
toggleModal = () => this.setState({ newModalOpen: !this.state.newModalOpen });
copyFunc = (obj: BeatObject) => this.setState({ copyObj: obj, newModalOpen: true, nameOrVersion: true });
versionFunc = (obj: BeatObject) => this.setState({ copyObj: obj, newModalOpen: true, nameOrVersion: false });
render () {
// name to be deleted if any
const deleteName = this.state.deleteModalData ? this.state.deleteModalData.name : '';
// need to find which objects can be versioned (which objects have no newer versions)
let versionableNames;
if(this.props.entity === 'experiment')
versionableNames = [];
else {
const getNameAndVersion = name => {
const segs = name.split('/');
const n = segs.slice(0, -1).join('/');
const version = Number.parseInt(segs[segs.length - 1]);
return [n, version];
};
// sort the names so that all versions of an object are together,
// and the versions are sorted from first to laste
const sortedNames = this.props.data.map(d => d.name).sort();
const nameAndVersions = sortedNames.map(n => getNameAndVersion(n));
// find the complete names of the highest versions of each obj
// since we sorted we just find the last version of each obj
versionableNames = sortedNames.filter((n, i) => {
if(i === sortedNames.length - 1)
return true;
if(nameAndVersions[i + 1][0] === nameAndVersions[i][0])
return false;
return true;
});
}
return (
<React.Fragment>
<ListGroup>
<NewEntityModal
toggle={this.toggleModal}
isOpen={this.state.newModalOpen}
entity={this.props.entity}
copyObj={this.state.copyObj}
nameOrVersion={this.state.nameOrVersion}
/>
{ this.props.data.map((d, i) =>
<El
key={i}
data={d}
entity={this.props.entity}
copyFunc={() => this.copyFunc(d)}
versionFunc={versionableNames.includes(d.name) ? () => this.versionFunc(d) : undefined}
deleteFunc={() => this.toggleDeleteModal(d)}
/>)
}
</ListGroup>
<Modal
toggle={() => this.toggleDeleteModal()}
isOpen={this.state.deleteModalData !== false}
>
<ModalHeader toggle={() => this.toggleDeleteModal()}>Confirm Deletion</ModalHeader>
<ModalBody>
Are you sure you want to permanently delete{' '}
<pre className='preInline'>
{ deleteName }
</pre>?
This will delete the object from your BEAT prefix as well as the web app.
{
this.props.entity === 'toolchain' &&
<React.Fragment>
<p>Deleting this toolchain will also delete the following experiments that depend on it:</p>
<ListGroup>
{
this.props.experiments
.filter(e => getTcFromExpName(e.name) === deleteName)
.map((e, i) => <ListGroupItem key={i}>{ e.name }</ListGroupItem>)
}
</ListGroup>
</React.Fragment>
}
</ModalBody>
<ModalFooter>
<Button color='danger'
onClick={async () => {
if(this.state.deleteModalData){
if(this.props.entity === 'toolchain'){
await Promise.all(
this.props.experiments
.filter(e => getTcFromExpName(e.name) === deleteName)
.map((e) => this.props.deleteFunc(e, 'experiment'))
);
}
await this.props.deleteFunc(this.state.deleteModalData);
}
this.toggleDeleteModal();
}}
>Delete</Button>
<Button color='secondary'
autoFocus
onClick={() => this.toggleDeleteModal()}
>Cancel</Button>
</ModalFooter>
</Modal>
</React.Fragment>
);
}
}
const mapStateToProps = (state, ownProps) => ({
data: Selectors[`${ ownProps.entity }Get`](state),
experiments: Selectors.experimentGet(state),
});
const mapDispatchToProps = (dispatch, ownProps) => ({
deleteFunc: (obj, entity = ownProps.entity) => dispatch(Actions.deleteObject(entity, obj)()),
});
export default connect(mapStateToProps, mapDispatchToProps)(EntityList);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment