Skip to content
Snippets Groups Projects
Commit 64e6e18d authored by piotr.kupczyk@id.ethz.ch's avatar piotr.kupczyk@id.ethz.ch
Browse files

Merge branch 'ssdm-10401-ng-ui-plugin-management-page' into master

parents 35877dd5 4614ab65
No related branches found
No related tags found
No related merge requests found
Showing
with 973 additions and 292 deletions
...@@ -28,6 +28,7 @@ module.exports = { ...@@ -28,6 +28,7 @@ module.exports = {
moment: '<rootDir>/srcV3/lib/moment/js/moment.js', moment: '<rootDir>/srcV3/lib/moment/js/moment.js',
stjs: '<rootDir>/srcV3/lib/stjs/js/stjs.js', stjs: '<rootDir>/srcV3/lib/stjs/js/stjs.js',
underscore: '<rootDir>/srcV3/lib/underscore/js/underscore.js', underscore: '<rootDir>/srcV3/lib/underscore/js/underscore.js',
'\\.css$': '<rootDir>/srcTest/js/mockStyles.js',
'openbis.js': '<rootDir>/srcTest/js/services/openbis.js', 'openbis.js': '<rootDir>/srcTest/js/services/openbis.js',
'^@src/(.*)$': '<rootDir>/src/$1', '^@src/(.*)$': '<rootDir>/src/$1',
'^@srcTest/(.*)$': '<rootDir>/srcTest/$1', '^@srcTest/(.*)$': '<rootDir>/srcTest/$1',
......
...@@ -12,12 +12,15 @@ ...@@ -12,12 +12,15 @@
"install": "^0.13.0", "install": "^0.13.0",
"npm": "^6.14.8", "npm": "^6.14.8",
"path-to-regexp": "^6.1.0", "path-to-regexp": "^6.1.0",
"prism-themes": "^1.5.0",
"prismjs": "^1.22.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"re-resizable": "^6.5.4", "re-resizable": "^6.5.4",
"react": "^16.13.1", "react": "^16.13.1",
"react-beautiful-dnd": "^13.0.0", "react-beautiful-dnd": "^13.0.0",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-redux": "^7.2.1", "react-redux": "^7.2.1",
"react-simple-code-editor": "^0.11.0",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-saga": "^1.1.3", "redux-saga": "^1.1.3",
"reselect": "^4.0.0", "reselect": "^4.0.0",
......
...@@ -5,6 +5,8 @@ const NEW_MATERIAL_TYPE = 'newMaterialType' ...@@ -5,6 +5,8 @@ const NEW_MATERIAL_TYPE = 'newMaterialType'
const NEW_VOCABULARY_TYPE = 'newVocabularyType' const NEW_VOCABULARY_TYPE = 'newVocabularyType'
const NEW_USER = 'newUser' const NEW_USER = 'newUser'
const NEW_USER_GROUP = 'newUserGroup' const NEW_USER_GROUP = 'newUserGroup'
const NEW_DYNAMIC_PROPERTY_PLUGIN = 'newDynamicPropertyPlugin'
const NEW_ENTITY_VALIDATION_PLUGIN = 'newEntityValidationPlugin'
const OBJECT_TYPE = 'objectType' const OBJECT_TYPE = 'objectType'
const COLLECTION_TYPE = 'collectionType' const COLLECTION_TYPE = 'collectionType'
...@@ -13,6 +15,8 @@ const MATERIAL_TYPE = 'materialType' ...@@ -13,6 +15,8 @@ const MATERIAL_TYPE = 'materialType'
const VOCABULARY_TYPE = 'vocabularyType' const VOCABULARY_TYPE = 'vocabularyType'
const USER = 'user' const USER = 'user'
const USER_GROUP = 'userGroup' const USER_GROUP = 'userGroup'
const DYNAMIC_PROPERTY_PLUGIN = 'dynamicPropertyPlugin'
const ENTITY_VALIDATION_PLUGIN = 'entityValidationPlugin'
const SEARCH = 'search' const SEARCH = 'search'
...@@ -24,6 +28,8 @@ export default { ...@@ -24,6 +28,8 @@ export default {
NEW_VOCABULARY_TYPE, NEW_VOCABULARY_TYPE,
NEW_USER, NEW_USER,
NEW_USER_GROUP, NEW_USER_GROUP,
NEW_DYNAMIC_PROPERTY_PLUGIN,
NEW_ENTITY_VALIDATION_PLUGIN,
OBJECT_TYPE, OBJECT_TYPE,
COLLECTION_TYPE, COLLECTION_TYPE,
DATA_SET_TYPE, DATA_SET_TYPE,
...@@ -31,5 +37,7 @@ export default { ...@@ -31,5 +37,7 @@ export default {
VOCABULARY_TYPE, VOCABULARY_TYPE,
USER, USER,
USER_GROUP, USER_GROUP,
DYNAMIC_PROPERTY_PLUGIN,
ENTITY_VALIDATION_PLUGIN,
SEARCH SEARCH
} }
const LOGIN = 'login' const LOGIN = 'login'
const TYPES = 'types' const TYPES = 'types'
const USERS = 'users' const USERS = 'users'
const TOOLS = 'tools'
export default { export default {
LOGIN, LOGIN,
TYPES, TYPES,
USERS USERS,
TOOLS
} }
...@@ -3,306 +3,160 @@ import { compile, match } from 'path-to-regexp' ...@@ -3,306 +3,160 @@ import { compile, match } from 'path-to-regexp'
import pages from '@src/js/common/consts/pages.js' import pages from '@src/js/common/consts/pages.js'
import objectTypes from '@src/js/common/consts/objectType.js' import objectTypes from '@src/js/common/consts/objectType.js'
function doFormat(actualParams, pattern, requiredParams) { class Route {
if (_.isMatch(actualParams, requiredParams)) { constructor(pattern, params) {
let toPath = compile(pattern, { encode: encodeURIComponent }) this.pattern = pattern
return { this.params = params
path: toPath(actualParams), }
match: Object.keys(requiredParams).length
format(params) {
if (_.isMatch(params, this.params)) {
let toPath = compile(this.pattern, { encode: encodeURIComponent })
return {
path: toPath(params),
specificity: Object.keys(this.params).length
}
} else {
return null
} }
} else {
return null
} }
}
function doParse(path, pattern, extraParams) { parse(path) {
let toPathObject = match(pattern, { decode: decodeURIComponent }) let toPathObject = match(this.pattern, { decode: decodeURIComponent })
let pathObject = toPathObject(path) let pathObject = toPathObject(path)
if (pathObject) { if (pathObject) {
return { return {
path: pathObject.path, path: pathObject.path,
...pathObject.params, ...pathObject.params,
...extraParams ...this.params
}
} else {
return null
} }
} else {
return null
} }
} }
const routes = { class DefaultRoute {
TYPES: { format() {
format: params => { return {
return doFormat(params, '/types', { path: '/',
page: pages.TYPES specificity: 0
})
},
parse: path => {
return doParse(path, '/types', {
page: pages.TYPES
})
}
},
TYPES_SEARCH: {
format: params => {
return doFormat(params, '/types-search/:id', {
page: pages.TYPES,
type: objectTypes.SEARCH
})
},
parse: path => {
return doParse(path, '/types-search/:id', {
page: pages.TYPES,
type: objectTypes.SEARCH
})
}
},
NEW_OBJECT_TYPE: {
format: params => {
return doFormat(params, '/new-object-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_OBJECT_TYPE
})
},
parse: path => {
return doParse(path, '/new-object-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_OBJECT_TYPE
})
}
},
OBJECT_TYPE: {
format: params => {
return doFormat(params, '/object-type/:id', {
page: pages.TYPES,
type: objectTypes.OBJECT_TYPE
})
},
parse: path => {
return doParse(path, '/object-type/:id', {
page: pages.TYPES,
type: objectTypes.OBJECT_TYPE
})
}
},
NEW_COLLECTION_TYPE: {
format: params => {
return doFormat(params, '/new-collection-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_COLLECTION_TYPE
})
},
parse: path => {
return doParse(path, '/new-collection-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_COLLECTION_TYPE
})
}
},
COLLECTION_TYPE: {
format: params => {
return doFormat(params, '/collection-type/:id', {
page: pages.TYPES,
type: objectTypes.COLLECTION_TYPE
})
},
parse: path => {
return doParse(path, '/collection-type/:id', {
page: pages.TYPES,
type: objectTypes.COLLECTION_TYPE
})
}
},
NEW_DATA_SET_TYPE: {
format: params => {
return doFormat(params, '/new-dataset-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_DATA_SET_TYPE
})
},
parse: path => {
return doParse(path, '/new-dataset-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_DATA_SET_TYPE
})
}
},
DATA_SET_TYPE: {
format: params => {
return doFormat(params, '/dataset-type/:id', {
page: pages.TYPES,
type: objectTypes.DATA_SET_TYPE
})
},
parse: path => {
return doParse(path, '/dataset-type/:id', {
page: pages.TYPES,
type: objectTypes.DATA_SET_TYPE
})
}
},
NEW_MATERIAL_TYPE: {
format: params => {
return doFormat(params, '/new-material-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_MATERIAL_TYPE
})
},
parse: path => {
return doParse(path, '/new-material-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_MATERIAL_TYPE
})
}
},
MATERIAL_TYPE: {
format: params => {
return doFormat(params, '/material-type/:id', {
page: pages.TYPES,
type: objectTypes.MATERIAL_TYPE
})
},
parse: path => {
return doParse(path, '/material-type/:id', {
page: pages.TYPES,
type: objectTypes.MATERIAL_TYPE
})
}
},
NEW_VOCABULARY_TYPE: {
format: params => {
return doFormat(params, '/new-vocabulary-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_VOCABULARY_TYPE
})
},
parse: path => {
return doParse(path, '/new-vocabulary-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_VOCABULARY_TYPE
})
}
},
VOCABULARY_TYPE: {
format: params => {
return doFormat(params, '/vocabulary-type/:id', {
page: pages.TYPES,
type: objectTypes.VOCABULARY_TYPE
})
},
parse: path => {
return doParse(path, '/vocabulary-type/:id', {
page: pages.TYPES,
type: objectTypes.VOCABULARY_TYPE
})
}
},
USERS: {
format: params => {
return doFormat(params, '/users', {
page: pages.USERS
})
},
parse: path => {
return doParse(path, '/users', {
page: pages.USERS
})
}
},
USERS_SEARCH: {
format: params => {
return doFormat(params, '/users-search/:id', {
page: pages.USERS,
type: objectTypes.SEARCH
})
},
parse: path => {
return doParse(path, '/users-search/:id', {
page: pages.USERS,
type: objectTypes.SEARCH
})
}
},
NEW_USER: {
format: params => {
return doFormat(params, '/new-user/:id', {
page: pages.USERS,
type: objectTypes.NEW_USER
})
},
parse: path => {
return doParse(path, '/new-user/:id', {
page: pages.USERS,
type: objectTypes.NEW_USER
})
}
},
USER: {
format: params => {
return doFormat(params, '/user/:id', {
page: pages.USERS,
type: objectTypes.USER
})
},
parse: path => {
return doParse(path, '/user/:id', {
page: pages.USERS,
type: objectTypes.USER
})
}
},
NEW_USER_GROUP: {
format: params => {
return doFormat(params, '/new-user-group/:id', {
page: pages.USERS,
type: objectTypes.NEW_USER_GROUP
})
},
parse: path => {
return doParse(path, '/new-user-group/:id', {
page: pages.USERS,
type: objectTypes.NEW_USER_GROUP
})
}
},
USER_GROUP: {
format: params => {
return doFormat(params, '/user-group/:id', {
page: pages.USERS,
type: objectTypes.USER_GROUP
})
},
parse: path => {
return doParse(path, '/user-group/:id', {
page: pages.USERS,
type: objectTypes.USER_GROUP
})
} }
}, }
DEFAULT: { parse() {
format: () => { return {
return { path: '/',
path: '/', page: pages.TYPES
match: 0
}
},
parse: () => {
return {
path: '/',
page: pages.TYPES
}
} }
} }
} }
const routes = {
TYPES: new Route('/types', {
page: pages.TYPES
}),
TYPES_SEARCH: new Route('/types-search/:id', {
page: pages.TYPES,
type: objectTypes.SEARCH
}),
NEW_OBJECT_TYPE: new Route('/new-object-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_OBJECT_TYPE
}),
OBJECT_TYPE: new Route('/object-type/:id', {
page: pages.TYPES,
type: objectTypes.OBJECT_TYPE
}),
NEW_COLLECTION_TYPE: new Route('/new-collection-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_COLLECTION_TYPE
}),
COLLECTION_TYPE: new Route('/collection-type/:id', {
page: pages.TYPES,
type: objectTypes.COLLECTION_TYPE
}),
NEW_DATA_SET_TYPE: new Route('/new-dataset-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_DATA_SET_TYPE
}),
DATA_SET_TYPE: new Route('/dataset-type/:id', {
page: pages.TYPES,
type: objectTypes.DATA_SET_TYPE
}),
NEW_MATERIAL_TYPE: new Route('/new-material-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_MATERIAL_TYPE
}),
MATERIAL_TYPE: new Route('/material-type/:id', {
page: pages.TYPES,
type: objectTypes.MATERIAL_TYPE
}),
NEW_VOCABULARY_TYPE: new Route('/new-vocabulary-type/:id', {
page: pages.TYPES,
type: objectTypes.NEW_VOCABULARY_TYPE
}),
VOCABULARY_TYPE: new Route('/vocabulary-type/:id', {
page: pages.TYPES,
type: objectTypes.VOCABULARY_TYPE
}),
USERS: new Route('/users', {
page: pages.USERS
}),
USERS_SEARCH: new Route('/users-search/:id', {
page: pages.USERS,
type: objectTypes.SEARCH
}),
NEW_USER: new Route('/new-user/:id', {
page: pages.USERS,
type: objectTypes.NEW_USER
}),
USER: new Route('/user/:id', {
page: pages.USERS,
type: objectTypes.USER
}),
NEW_USER_GROUP: new Route('/new-user-group/:id', {
page: pages.USERS,
type: objectTypes.NEW_USER_GROUP
}),
USER_GROUP: new Route('/user-group/:id', {
page: pages.USERS,
type: objectTypes.USER_GROUP
}),
TOOLS: new Route('/tools', {
page: pages.TOOLS
}),
TOOLS_SEARCH: new Route('/tools-search/:id', {
page: pages.TOOLS,
type: objectTypes.SEARCH
}),
NEW_DYNAMIC_PROPERTY_PLUGIN: new Route('/new-dynamic-property-plugin/:id', {
page: pages.TOOLS,
type: objectTypes.NEW_DYNAMIC_PROPERTY_PLUGIN
}),
DYNAMIC_PROPERTY_PLUGIN: new Route('/dynamic-property-plugin/:id', {
page: pages.TOOLS,
type: objectTypes.DYNAMIC_PROPERTY_PLUGIN
}),
NEW_ENTITY_VALIDATION_PLUGIN: new Route('/new-entity-validation-plugin/:id', {
page: pages.TOOLS,
type: objectTypes.NEW_ENTITY_VALIDATION_PLUGIN
}),
ENTITY_VALIDATION_PLUGIN: new Route('/entity-validation-plugin/:id', {
page: pages.TOOLS,
type: objectTypes.ENTITY_VALIDATION_PLUGIN
}),
DEFAULT: new DefaultRoute()
}
function format(params) { function format(params) {
let keys = Object.keys(routes) let keys = Object.keys(routes)
let best = { match: 0, path: null } let best = { specificity: 0, path: null }
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
let route = routes[keys[i]] let route = routes[keys[i]]
try { try {
let result = route.format(params) let result = route.format(params)
if (result && result.match > best.match) { if (result && result.specificity > best.specificity) {
best = result best = result
} }
} catch (err) { } catch (err) {
......
...@@ -15,6 +15,7 @@ import Menu from '@src/js/components/common/menu/Menu.jsx' ...@@ -15,6 +15,7 @@ import Menu from '@src/js/components/common/menu/Menu.jsx'
import Login from '@src/js/components/login/Login.jsx' import Login from '@src/js/components/login/Login.jsx'
import Users from '@src/js/components/users/Users.jsx' import Users from '@src/js/components/users/Users.jsx'
import Types from '@src/js/components/types/Types.jsx' import Types from '@src/js/components/types/Types.jsx'
import Tools from '@src/js/components/tools/Tools.jsx'
const styles = { const styles = {
container: { container: {
...@@ -37,7 +38,8 @@ const styles = { ...@@ -37,7 +38,8 @@ const styles = {
const pageToComponent = { const pageToComponent = {
[pages.TYPES]: Types, [pages.TYPES]: Types,
[pages.USERS]: Users [pages.USERS]: Users,
[pages.TOOLS]: Tools
} }
function mapStateToProps(state) { function mapStateToProps(state) {
......
...@@ -11,10 +11,7 @@ const styles = theme => ({ ...@@ -11,10 +11,7 @@ const styles = theme => ({
paddingBottom: theme.spacing(1) / 2, paddingBottom: theme.spacing(1) / 2,
borderBottomWidth: '1px', borderBottomWidth: '1px',
borderBottomStyle: 'solid', borderBottomStyle: 'solid',
borderBottomColor: theme.palette.border.secondary, borderBottomColor: theme.palette.border.secondary
'&:after': {
content: '"\\00a0"'
}
} }
}) })
...@@ -23,11 +20,11 @@ class FormFieldView extends React.PureComponent { ...@@ -23,11 +20,11 @@ class FormFieldView extends React.PureComponent {
const { label, value, classes } = this.props const { label, value, classes } = this.props
return ( return (
<div> <div>
<Typography variant='body2' className={classes.label}> <Typography variant='body2' component='div' className={classes.label}>
{label} {label}
</Typography> </Typography>
<Typography variant='body2' className={classes.value}> <Typography variant='body2' component='div' className={classes.value}>
{value ? value : ''} {value ? value : <span>&nbsp;</span>}
</Typography> </Typography>
</div> </div>
) )
......
import React from 'react'
import autoBind from 'auto-bind'
import { withStyles } from '@material-ui/core/styles'
import { highlight, languages } from 'prismjs/components/prism-core.js'
import 'prismjs/components/prism-clike.js'
import 'prismjs/components/prism-python.js'
import 'prismjs/themes/prism.css'
import Editor from 'react-simple-code-editor'
import InputLabel from '@material-ui/core/InputLabel'
import FormFieldLabel from '@src/js/components/common/form/FormFieldLabel.jsx'
import FormFieldContainer from '@src/js/components/common/form/FormFieldContainer.jsx'
import FormFieldView from '@src/js/components/common/form/FormFieldView.jsx'
import logger from '@src/js/common/logger.js'
const styles = theme => ({
view: {
fontFamily: theme.typography.sourceCode.fontFamily,
fontSize: theme.typography.body2.fontSize,
whiteSpace: 'pre-wrap',
tabSize: 4
},
edit: {
fontFamily: theme.typography.sourceCode.fontFamily,
fontSize: theme.typography.body2.fontSize,
lineHeight: theme.typography.body2.lineHeight,
backgroundColor: theme.palette.background.field,
tabSize: 4,
'& *': {
background: 'none !important'
},
'& textarea': {
padding: '23px 12px 6px 12px !important',
border: `1px solid ${theme.palette.border.primary} !important`,
borderBottom: `1px solid ${theme.palette.border.field} !important`,
outline: 'none !important'
},
'& textarea:focus': {
borderBottom: `2px solid ${theme.palette.primary.main} !important`
},
'& pre': {
padding: '23px 12px 6px 12px !important'
}
},
error: {
'&$edit textarea': {
borderBottom: `2px solid ${theme.palette.error.main} !important`
}
},
disabled: {
'&$edit pre': {
opacity: 0.5
}
}
})
class SourceCodeField extends React.PureComponent {
static defaultProps = {
mode: 'edit',
variant: 'filled'
}
constructor(props) {
super(props)
autoBind(this)
this.state = { focused: false }
this.containerRef = React.createRef()
}
handleValueChange(value) {
const { name, onChange } = this.props
if (onChange) {
onChange({
target: {
name,
value
}
})
}
}
handleFocus() {
this.setState({
focused: true
})
const { onFocus } = this.props
if (onFocus) {
onFocus(event)
}
}
handleBlur(event) {
this.setState({
focused: false
})
const { onBlur } = this.props
if (onBlur) {
onBlur(event)
}
}
componentDidUpdate() {
const { reference } = this.props
if (reference) {
const containerElement = this.containerRef.current
if (containerElement) {
reference.current = containerElement.querySelector('textarea')
}
}
}
render() {
logger.log(logger.DEBUG, 'SourceCodeField.render')
const { mode } = this.props
if (mode === 'view') {
return this.renderView()
} else if (mode === 'edit') {
return this.renderEdit()
} else {
throw 'Unsupported mode: ' + mode
}
}
renderView() {
const { label, value, classes } = this.props
const html = { __html: highlight(value || '', languages.python) }
return (
<FormFieldView
label={label}
value={<div className={classes.view} dangerouslySetInnerHTML={html} />}
/>
)
}
renderEdit() {
const {
name,
label,
value,
description,
mandatory,
disabled,
error,
variant,
onClick,
styles,
classes
} = this.props
const { focused } = this.state
return (
<FormFieldContainer
description={description}
error={error}
styles={styles}
onClick={onClick}
>
<div
ref={this.containerRef}
className={`
${classes.edit}
${error ? classes.error : ''}
${disabled ? classes.disabled : ''}
`}
>
<InputLabel
shrink={!!value || focused}
error={!!error}
variant={variant}
margin='dense'
>
<FormFieldLabel
label={label}
mandatory={mandatory}
styles={styles}
onClick={onClick}
/>
</InputLabel>
<Editor
name={name}
value={value || ''}
highlight={code => highlight(code, languages.python)}
disabled={disabled}
onValueChange={this.handleValueChange}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
/>
</div>
</FormFieldContainer>
)
}
}
export default withStyles(styles)(SourceCodeField)
...@@ -113,6 +113,7 @@ class Menu extends React.Component { ...@@ -113,6 +113,7 @@ class Menu extends React.Component {
> >
<Tab value={pages.TYPES} label='Types' /> <Tab value={pages.TYPES} label='Types' />
<Tab value={pages.USERS} label='Users' /> <Tab value={pages.USERS} label='Users' />
<Tab value={pages.TOOLS} label='Tools' />
</Tabs> </Tabs>
<TextField <TextField
placeholder='Search...' placeholder='Search...'
......
...@@ -9,6 +9,9 @@ const config = { ...@@ -9,6 +9,9 @@ const config = {
label: { label: {
fontSize: '0.7rem', fontSize: '0.7rem',
color: '#0000008a' color: '#0000008a'
},
sourceCode: {
fontFamily: '"Fira code", "Fira Mono", monospace'
} }
}, },
palette: { palette: {
...@@ -29,11 +32,13 @@ const config = { ...@@ -29,11 +32,13 @@ const config = {
}, },
background: { background: {
primary: '#ebebeb', primary: '#ebebeb',
secondary: '#f5f5f5' secondary: '#f5f5f5',
field: '#e8e8e8'
}, },
border: { border: {
primary: '#dbdbdb', primary: '#dbdbdb',
secondary: '#ebebeb' secondary: '#ebebeb',
field: '#878787'
} }
} }
} }
......
import React from 'react'
import { withStyles } from '@material-ui/core/styles'
import logger from '@src/js/common/logger.js'
import pages from '@src/js/common/consts/pages.js'
import objectType from '@src/js/common/consts/objectType.js'
import Content from '@src/js/components/common/content/Content.jsx'
import ContentTab from '@src/js/components/common/content/ContentTab.jsx'
import ToolBrowser from '@src/js/components/tools/browser/ToolBrowser.jsx'
import ToolSearch from '@src/js/components/tools/search/ToolSearch.jsx'
import PluginForm from '@src/js/components/tools/form/plugin/PluginForm.jsx'
const styles = () => ({
container: {
display: 'flex',
width: '100%'
}
})
class Tools extends React.Component {
render() {
logger.log(logger.DEBUG, 'Tools.render')
const classes = this.props.classes
return (
<div className={classes.container}>
<ToolBrowser />
<Content
page={pages.TOOLS}
renderComponent={this.renderComponent}
renderTab={this.renderTab}
/>
</div>
)
}
renderComponent(tab) {
const { object } = tab
if (
object.type === objectType.NEW_DYNAMIC_PROPERTY_PLUGIN ||
object.type === objectType.NEW_ENTITY_VALIDATION_PLUGIN ||
object.type === objectType.DYNAMIC_PROPERTY_PLUGIN ||
object.type === objectType.ENTITY_VALIDATION_PLUGIN
) {
return <PluginForm object={object} />
} else if (object.type === objectType.SEARCH) {
return <ToolSearch objectId={object.id} />
}
}
renderTab(tab) {
const { object } = tab
const prefixes = {
[objectType.DYNAMIC_PROPERTY_PLUGIN]: 'Dynamic Property Plugin: ',
[objectType.NEW_DYNAMIC_PROPERTY_PLUGIN]: 'New Dynamic Property Plugin ',
[objectType.ENTITY_VALIDATION_PLUGIN]: 'Entity Validation Plugin: ',
[objectType.NEW_ENTITY_VALIDATION_PLUGIN]:
'New Entity Validation Plugin ',
[objectType.SEARCH]: 'Search: '
}
return <ContentTab prefix={prefixes[object.type]} tab={tab} />
}
}
export default withStyles(styles)(Tools)
import React from 'react'
import Browser from '@src/js/components/common/browser/Browser.jsx'
import ToolBrowserController from '@src/js/components/tools/browser/ToolBrowserController.js'
import logger from '@src/js/common/logger.js'
class ToolBrowser extends React.Component {
constructor(props) {
super(props)
this.controller = this.props.controller || new ToolBrowserController()
}
render() {
logger.log(logger.DEBUG, 'ToolBrowser.render')
return <Browser controller={this.controller} />
}
}
export default ToolBrowser
import openbis from '@src/js/services/openbis.js'
import actions from '@src/js/store/actions/actions.js'
import pages from '@src/js/common/consts/pages.js'
import objectType from '@src/js/common/consts/objectType.js'
import objectOperation from '@src/js/common/consts/objectOperation.js'
import BrowserController from '@src/js/components/common/browser/BrowserController.js'
export default class ToolBrowserController extends BrowserController {
doGetPage() {
return pages.TOOLS
}
async doLoadNodes() {
return Promise.all([
openbis.searchPlugins(
new openbis.PluginSearchCriteria(),
new openbis.PluginFetchOptions()
)
]).then(([plugins]) => {
const dynamicPropertyPluginNodes = plugins
.getObjects()
.filter(
plugin => plugin.pluginType === openbis.PluginType.DYNAMIC_PROPERTY
)
.map(plugin => {
return {
id: `dynamicPropertyPlugin/${plugin.name}`,
text: plugin.name,
object: {
type: objectType.DYNAMIC_PROPERTY_PLUGIN,
id: plugin.name
},
canMatchFilter: true,
canRemove: true
}
})
const entityValidationPluginNodes = plugins
.getObjects()
.filter(
plugin => plugin.pluginType === openbis.PluginType.ENTITY_VALIDATION
)
.map(plugin => {
return {
id: `entityValidationPlugin/${plugin.name}`,
text: plugin.name,
object: {
type: objectType.ENTITY_VALIDATION_PLUGIN,
id: plugin.name
},
canMatchFilter: true,
canRemove: true
}
})
let nodes = [
{
id: 'dynamicPropertyPlugins',
text: 'Dynamic Property Plugins',
children: dynamicPropertyPluginNodes,
childrenType: objectType.NEW_DYNAMIC_PROPERTY_PLUGIN,
canAdd: true
},
{
id: 'entityValidationPlugins',
text: 'Entity Validation Plugins',
children: entityValidationPluginNodes,
childrenType: objectType.NEW_ENTITY_VALIDATION_PLUGIN,
canAdd: true
}
]
return nodes
})
}
doNodeAdd(node) {
if (node && node.childrenType) {
this.context.dispatch(
actions.objectNew(this.getPage(), node.childrenType)
)
}
}
doNodeRemove(node) {
if (!node.object) {
return Promise.resolve()
}
const { type, id } = node.object
const reason = 'deleted via ng_ui'
return this._prepareRemoveOperations(type, id, reason)
.then(operations => {
const options = new openbis.SynchronousOperationExecutionOptions()
options.setExecuteInOrder(true)
return openbis.executeOperations(operations, options)
})
.then(() => {
this.context.dispatch(actions.objectDelete(this.getPage(), type, id))
})
.catch(error => {
this.context.dispatch(actions.errorChange(error))
})
}
_prepareRemoveOperations(type, id, reason) {
if (
type === objectType.DYNAMIC_PROPERTY_PLUGIN ||
type === objectType.ENTITY_VALIDATION_PLUGIN
) {
return this._prepareRemovePluginOperations(id, reason)
} else {
throw new Error('Unsupported type: ' + type)
}
}
_prepareRemovePluginOperations(id, reason) {
const options = new openbis.PluginDeletionOptions()
options.setReason(reason)
return Promise.resolve([
new openbis.DeletePluginsOperation(
[new openbis.PluginPermId(id)],
options
)
])
}
doGetObservedModifications() {
return {
[objectType.DYNAMIC_PROPERTY_PLUGIN]: [
objectOperation.CREATE,
objectOperation.DELETE
],
[objectType.ENTITY_VALIDATION_PLUGIN]: [
objectOperation.CREATE,
objectOperation.DELETE
]
}
}
}
import _ from 'lodash'
import React from 'react'
import autoBind from 'auto-bind'
import { connect } from 'react-redux'
import { withStyles } from '@material-ui/core/styles'
import ComponentContext from '@src/js/components/common/ComponentContext.js'
import PageWithTwoPanels from '@src/js/components/common/page/PageWithTwoPanels.jsx'
import PluginFormController from '@src/js/components/tools/form/plugin/PluginFormController.js'
import PluginFormFacade from '@src/js/components/tools/form/plugin/PluginFormFacade.js'
import PluginFormScript from '@src/js/components/tools/form/plugin/PluginFormScript.jsx'
import PluginFormParameters from '@src/js/components/tools/form/plugin/PluginFormParameters.jsx'
import PluginFormButtons from '@src/js/components/tools/form/plugin/PluginFormButtons.jsx'
import openbis from '@src/js/services/openbis.js'
import logger from '@src/js/common/logger.js'
const styles = () => ({})
class PluginForm extends React.PureComponent {
constructor(props) {
super(props)
autoBind(this)
this.state = {}
if (this.props.controller) {
this.controller = this.props.controller
} else {
this.controller = new PluginFormController(new PluginFormFacade())
}
this.controller.init(new ComponentContext(this))
}
componentDidMount() {
this.controller.load()
}
render() {
logger.log(logger.DEBUG, 'PluginForm.render')
const { loading, loaded, plugin } = this.state
return (
<PageWithTwoPanels
loading={loading}
loaded={loaded}
object={plugin}
renderMainPanel={() => this.renderMainPanel()}
renderAdditionalPanel={() => this.renderAdditionalPanel()}
renderButtons={() => this.renderButtons()}
/>
)
}
renderMainPanel() {
const { controller } = this
const { plugin, selection, mode } = this.state
if (plugin.pluginKind === openbis.PluginKind.JYTHON) {
return (
<PluginFormScript
plugin={plugin}
selection={selection}
mode={mode}
onChange={controller.handleChange}
onSelectionChange={controller.handleSelectionChange}
onBlur={controller.handleBlur}
/>
)
} else {
return <div></div>
}
}
renderAdditionalPanel() {
const { controller } = this
const { plugin, selection, mode } = this.state
return (
<PluginFormParameters
plugin={plugin}
selection={selection}
mode={mode}
onChange={controller.handleChange}
onSelectionChange={controller.handleSelectionChange}
onBlur={controller.handleBlur}
/>
)
}
renderButtons() {
const { controller } = this
const { plugin, changed, mode } = this.state
return (
<PluginFormButtons
onEdit={controller.handleEdit}
onSave={controller.handleSave}
onCancel={controller.handleCancel}
plugin={plugin}
changed={changed}
mode={mode}
/>
)
}
}
export default _.flow(connect(), withStyles(styles))(PluginForm)
import React from 'react'
import PageButtons from '@src/js/components/common/page/PageButtons.jsx'
import openbis from '@src/js/services/openbis.js'
import logger from '@src/js/common/logger.js'
class PluginFormButtons extends React.PureComponent {
render() {
logger.log(logger.DEBUG, 'PluginFormButtons.render')
const { mode, onEdit, onSave, onCancel, changed, plugin } = this.props
return (
<PageButtons
mode={mode}
changed={changed}
onEdit={plugin.pluginKind === openbis.PluginKind.JYTHON ? onEdit : null}
onSave={onSave}
onCancel={plugin.id ? onCancel : null}
/>
)
}
}
export default PluginFormButtons
import PageController from '@src/js/components/common/page/PageController.js'
import PluginFormControllerLoad from '@src/js/components/tools/form/plugin/PluginFormControllerLoad.js'
import PluginFormControllerValidate from '@src/js/components/tools/form/plugin/PluginFormControllerValidate.js'
import PluginFormControllerChange from '@src/js/components/tools/form/plugin/PluginFormControllerChange.js'
import PluginFormControllerSave from '@src/js/components/tools/form/plugin/PluginFormControllerSave.js'
import pages from '@src/js/common/consts/pages.js'
import objectTypes from '@src/js/common/consts/objectType.js'
export default class PluginFormController extends PageController {
constructor(facade) {
super(facade)
}
getPage() {
return pages.TOOLS
}
isDynamicPropertyType() {
return (
this.object.type === objectTypes.DYNAMIC_PROPERTY_PLUGIN ||
this.object.type === objectTypes.NEW_DYNAMIC_PROPERTY_PLUGIN
)
}
isEntityValidationType() {
return (
this.object.type === objectTypes.ENTITY_VALIDATION_PLUGIN ||
this.object.type === objectTypes.NEW_ENTITY_VALIDATION_PLUGIN
)
}
getNewObjectType() {
if (this.isDynamicPropertyType()) {
return objectTypes.NEW_DYNAMIC_PROPERTY_PLUGIN
} else if (this.isEntityValidationType()) {
return objectTypes.NEW_ENTITY_VALIDATION_PLUGIN
} else {
throw new Error('Unsupported object type: ' + this.object.type)
}
}
getExistingObjectType() {
if (this.isDynamicPropertyType()) {
return objectTypes.DYNAMIC_PROPERTY_PLUGIN
} else if (this.isEntityValidationType()) {
return objectTypes.ENTITY_VALIDATION_PLUGIN
} else {
throw new Error('Unsupported object type: ' + this.object.type)
}
}
load() {
return new PluginFormControllerLoad(this).execute()
}
validate(autofocus) {
return new PluginFormControllerValidate(this).execute(autofocus)
}
handleChange(type, params) {
return new PluginFormControllerChange(this).execute(type, params)
}
handleSave() {
return new PluginFormControllerSave(this).execute()
}
}
import PageControllerChange from '@src/js/components/common/page/PageControllerChange.js'
import PluginFormSelectionType from '@src/js/components/tools/form/plugin/PluginFormSelectionType.js'
import FormUtil from '@src/js/components/common/form/FormUtil.js'
export default class PluginFormControllerChange extends PageControllerChange {
async execute(type, params) {
if (type === PluginFormSelectionType.PLUGIN) {
await this._handleChangePlugin(params)
}
}
async _handleChangePlugin(params) {
await this.context.setState(state => {
const { newObject } = FormUtil.changeObjectField(
state.plugin,
params.field,
params.value
)
return {
plugin: newObject
}
})
await this.controller.changed(true)
}
}
import _ from 'lodash'
import PageControllerLoad from '@src/js/components/common/page/PageControllerLoad.js'
import FormUtil from '@src/js/components/common/form/FormUtil.js'
import openbis from '@src/js/services/openbis.js'
export default class PluginFormControllerLoad extends PageControllerLoad {
async load(object, isNew) {
let loadedPlugin = null
if (!isNew) {
loadedPlugin = await this.facade.loadPlugin(object.id)
if (!loadedPlugin) {
return
}
}
const plugin = this._createPlugin(object, loadedPlugin)
return this.context.setState({
plugin
})
}
_createPlugin(object, loadedPlugin) {
let pluginKind = null
let pluginType = null
if (loadedPlugin) {
pluginKind = _.get(loadedPlugin, 'pluginKind')
pluginType = _.get(loadedPlugin, 'pluginType')
} else {
pluginKind = openbis.PluginKind.JYTHON
if (this.controller.isDynamicPropertyType()) {
pluginType = openbis.PluginType.DYNAMIC_PROPERTY
} else if (this.controller.isEntityValidationType()) {
pluginType = openbis.PluginType.ENTITY_VALIDATION
} else {
throw new Error('Unsupported object type: ' + object.type)
}
}
const entityKinds = _.get(loadedPlugin, 'entityKinds', [])
const plugin = {
id: _.get(loadedPlugin, 'name', null),
pluginKind,
pluginType,
name: FormUtil.createField({
value: _.get(loadedPlugin, 'name', null),
enabled: loadedPlugin === null
}),
entityKind: FormUtil.createField({
value: entityKinds.length === 1 ? entityKinds[0] : null,
enabled: loadedPlugin === null
}),
description: FormUtil.createField({
value: _.get(loadedPlugin, 'description', null)
}),
script: FormUtil.createField({
value: _.get(loadedPlugin, 'script', null)
}),
available: FormUtil.createField({
value: _.get(loadedPlugin, 'available', true)
})
}
if (loadedPlugin) {
plugin.original = _.cloneDeep(plugin)
}
return plugin
}
}
import PageControllerSave from '@src/js/components/common/page/PageControllerSave.js'
import FormUtil from '@src/js/components/common/form/FormUtil.js'
import openbis from '@src/js/services/openbis.js'
export default class PluginFormControllerSave extends PageControllerSave {
async save() {
const state = this.context.getState()
const plugin = FormUtil.trimFields({ ...state.plugin })
const operations = []
if (plugin.original) {
if (this._isPluginUpdateNeeded(plugin)) {
operations.push(this._updatePluginOperation(plugin))
}
} else {
operations.push(this._createPluginOperation(plugin))
}
const options = new openbis.SynchronousOperationExecutionOptions()
options.setExecuteInOrder(true)
await this.facade.executeOperations(operations, options)
return plugin.name.value
}
_isPluginUpdateNeeded(plugin) {
return FormUtil.haveFieldsChanged(plugin, plugin.original, [
'description',
'script'
])
}
_createPluginOperation(plugin) {
const creation = new openbis.PluginCreation()
creation.setPluginType(plugin.pluginType)
creation.setEntityKind(plugin.entityKind.value)
creation.setName(plugin.name.value)
creation.setDescription(plugin.description.value)
creation.setScript(plugin.script.value)
return new openbis.CreatePluginsOperation([creation])
}
_updatePluginOperation(plugin) {
const update = new openbis.PluginUpdate()
update.setPluginId(new openbis.PluginPermId(plugin.name.value))
update.setDescription(plugin.description.value)
update.setScript(plugin.script.value)
return new openbis.UpdatePluginsOperation([update])
}
}
import PageControllerValidate from '@src/js/components/common/page/PageConrollerValidate.js'
import PluginFormSelectionType from '@src/js/components/tools/form/plugin/PluginFormSelectionType.js'
export default class PluginFormControllerValidate extends PageControllerValidate {
validate(validator) {
const { plugin } = this.context.getState()
const newPlugin = this._validatePlugin(validator, plugin)
return {
plugin: newPlugin
}
}
async select(firstError) {
const { plugin } = this.context.getState()
if (firstError.object === plugin) {
await this.setSelection({
type: PluginFormSelectionType.PLUGIN,
params: {
part: firstError.name
}
})
}
}
_validatePlugin(validator, plugin) {
validator.validateNotEmpty(plugin, 'name', 'Name')
validator.validateNotEmpty(plugin, 'script', 'Script')
return validator.withErrors(plugin)
}
}
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