Skip to content

Commit

Permalink
Reference Form with search owner/source/collection/concepts/mappings …
Browse files Browse the repository at this point in the history
…options
  • Loading branch information
snyaggarwal committed Jan 21, 2021
1 parent 0c7c5da commit 880a0ba
Show file tree
Hide file tree
Showing 12 changed files with 376 additions and 71 deletions.
1 change: 1 addition & 0 deletions src/components/collections/CollectionHomeTabs.jsx
Expand Up @@ -93,6 +93,7 @@ const CollectionHomeTabs = props => {
<CommonFormDrawer
isOpen={referenceForm}
onClose={() => setReferenceForm(false)}
size='large'
formComponent={
<ReferenceForm onCancel={() => setReferenceForm(false)} reloadOnSuccess={tab < 3} parentURL={versionedObjectURL} collection={collection} />
}
Expand Down
67 changes: 47 additions & 20 deletions src/components/collections/ReferenceForm.jsx
@@ -1,10 +1,11 @@
import React from 'react';
import alertifyjs from 'alertifyjs';
import { Button, Switch, FormControlLabel } from '@material-ui/core';
import { set, get, isEmpty, isNumber, isNaN, cloneDeep, pullAt, find, map } from 'lodash';
import { set, get, isEmpty, isNumber, isNaN, cloneDeep, pullAt, find, map, compact } from 'lodash';
import APIService from '../../services/APIService';
import { SOURCE_CHILD_URI_REGEX } from '../../common/constants';
import URLReferenceForm from './URLReferenceForm';
import ResourceReferenceForm from './ResourceReferenceForm';
const EXPRESSION_MODEL = {uri: '', valid: false, count: undefined, error: ''}

class ReferenceForm extends React.Component {
Expand All @@ -13,17 +14,36 @@ class ReferenceForm extends React.Component {
this.expressionRegex = new RegExp(SOURCE_CHILD_URI_REGEX);
this.state = {
byURL: false,
owners: [],
fields: {
concepts: [],
mappings: [],
expressions: [cloneDeep(EXPRESSION_MODEL)],
expression: '',
},
fieldErrors: {},
}
}

onSwitchChange = event => this.setState({byURL: event.target.checked})
componentDidMount() {
this.fetchOwners();
}

fetchOwners() {
APIService.orgs().get().then(response => {
const orgs = map(response.data, org => ({...org, ownerType: 'org', name: org.id}))
this.setState({owners: [...this.state.owners, ...orgs]})
})

APIService.users().get().then(response => {
const users = map(response.data, user => ({...user, ownerType: 'user', name: user.username}))
this.setState({owners: [...this.state.owners, ...users]})
})
}

onSwitchChange = event => this.setState({
byURL: event.target.checked,
fields: {...this.state.fields, expressions: [cloneDeep(EXPRESSION_MODEL)]}
})

onTextFieldChange = event => this.setFieldValue(event.target.id, event.target.value)

Expand Down Expand Up @@ -91,13 +111,15 @@ class ReferenceForm extends React.Component {
event.preventDefault();
event.stopPropagation();

const form = document.getElementsByTagName('form')[0];
form.reportValidity()
const isFormValid = form.checkValidity()
if(isFormValid && !this.anyInvalidExpression()) {
const fields = cloneDeep(this.state.fields);
const expressions = compact(map(fields.expressions, 'uri'))
if(isEmpty(expressions)) {
alertifyjs.error('Please add/select reference(s) to add')
return
}

if(!this.anyInvalidExpression()) {
const { parentURL } = this.props
const fields = cloneDeep(this.state.fields);
const expressions = map(fields.expressions, 'uri')
APIService.new().overrideURL(parentURL).appendToUrl('references/').put({data: {expressions: expressions}}).then(response => this.handleSubmitResponse(response))
}
}
Expand Down Expand Up @@ -125,8 +147,13 @@ class ReferenceForm extends React.Component {
}
}

onExpressionChange = expressions => {
const refs = map(expressions, expression => ({...cloneDeep(EXPRESSION_MODEL), uri: expression}))
this.setState({fields: {...this.state.fields, expressions: refs}})
}

render() {
const { byURL, fields } = this.state;
const { byURL, fields, owners } = this.state;
const { onCancel } = this.props;
const header = `Add Reference(s)`;
return (
Expand All @@ -135,13 +162,21 @@ class ReferenceForm extends React.Component {
<h2>{header}</h2>
</div>
<div className='col-md-12 no-side-padding'>
<div className='col-md-12'>
<div className='col-md-3'>
<FormControlLabel
control={<Switch checked={byURL} onChange={this.onSwitchChange} color='primary' name="byURL" />}
label="Add by URL"
style={{marginBottom: '20px'}}
/>
</div>
<div className='col-md-9' style={{textAlign: 'right'}}>
<Button style={{margin: '0 10px'}} color='primary' variant='outlined' type='submit' onClick={this.onSubmit}>
Add
</Button>
<Button style={{margin: '0 10px'}} variant='outlined' onClick={onCancel}>
Cancel
</Button>
</div>
<form>
{
byURL ?
Expand All @@ -152,16 +187,8 @@ class ReferenceForm extends React.Component {
onBlur={this.onExpressionBlur}
onDelete={this.onExpressionDelete}
/> :
<div></div>
<ResourceReferenceForm owners={owners} onChange={this.onExpressionChange} />
}
<div className='col-md-12' style={{textAlign: 'center', margin: '15px 0'}}>
<Button style={{margin: '0 10px'}} color='primary' variant='outlined' type='submit' onClick={this.onSubmit}>
Add
</Button>
<Button style={{margin: '0 10px'}} variant='outlined' onClick={onCancel}>
Cancel
</Button>
</div>
</form>
</div>
</div>
Expand Down
170 changes: 170 additions & 0 deletions src/components/collections/ResourceReferenceForm.jsx
@@ -0,0 +1,170 @@
import React from 'react';
import { TextField } from '@material-ui/core';
import Autocomplete from '@material-ui/lab/Autocomplete';
import { set, map, orderBy, get, includes } from 'lodash';
import MixedOwnersAutocomplete from '../common/MixedOwnersAutocomplete';
import ConceptContainersAutocomplete from '../common/ConceptContainersAutocomplete';
import APIService from '../../services/APIService';
import Search from '../search/Search';

class ResourceReferenceForm extends React.Component {
constructor(props) {
super(props);
this.state = {
versions: [],
sources: [],
collections: [],
concepts: [],
mappings: [],
owner: null,
source: null,
collection: null,
version: null,
expressions: [],
}
}

onAutoCompleteChange = (id, item) => {
this.setFieldValue(id, item)
}

setFieldValue(id, value) {
const newState = {...this.state}
set(newState, id, value)
if(id === 'owner') {
set(newState, 'source', null)
set(newState, 'collection', null)
set(newState, 'sources', [])
set(newState, 'collections', [])
}
if (includes(['source', 'collection', 'owner'], id)) {
set(newState, 'version', null)
set(newState, 'versions', [])
}
if(id === 'source' && !value)
newState.collection = null

this.setState(newState, () => {
if(id === 'owner')
this.fetchConceptContainers()
if(includes(['source', 'collection'], id))
this.fetchVersions()
})
}

fetchConceptContainers() {
const { owner } = this.state

if(owner) {
APIService
.new()
.overrideURL(owner.url)
.appendToUrl('sources/')
.get()
.then(response => this.setState({
sources: map(response.data, source => ({...source, type: 'source'}))
}))
APIService
.new()
.overrideURL(owner.url)
.appendToUrl('collections/')
.get()
.then(response => this.setState({
collections: map(response.data, collection => ({...collection, type: 'collection'}))
}))
}
}

fetchVersions() {
const { source, collection } = this.state;
const container = source || collection;
if(container) {
APIService.new().overrideURL(container.url).appendToUrl('versions/').get().then(response => this.setState({versions: response.data}))
}
}

onSelectChange = (resource, selectedList) => {
this.setState(
{[resource]: selectedList},
() => this.props.onChange([...this.state.concepts, ...this.state.mappings])
)
}

render() {
const { owners } = this.props;
const {
owner, source, collection, sources, collections, version, versions
} = this.state;
return (
<div className='col-md-12 no-side-padding'>
<div className='col-md-4 no-left-padding'>
<MixedOwnersAutocomplete
owners={orderBy(owners, ['ownerType', 'name'])}
selected={owner}
onChange={this.onAutoCompleteChange}
required
/>
</div>
<div className='col-md-6 no-left-padding'>
<ConceptContainersAutocomplete
items={orderBy([...sources, ...collections], ['type', 'name'], ['desc', 'asc'])}
selected={source || collection}
onChange={this.onAutoCompleteChange}
required
/>
</div>
<div className='col-md-2 no-side-padding' style={{width: '16.66666%', minWidth: '16.66666%'}}>
<Autocomplete
blurOnSelect
getOptionSelected={(option, value) => option.version === get(value, 'version')}
value={version}
id='version'
options={versions}
getOptionLabel={option => option.version}
fullWidth
required
renderInput={
params => <TextField {...params} required label='Version' variant="outlined" fullWidth />
}
onChange={(event, item) => this.onAutoCompleteChange('version', item)}
/>
</div>
{
version &&
<React.Fragment>
<div className='col-md-6 no-side-padding' style={{marginTop: '15px', width: '50%', minWidth: '50%', borderRight: '1px solid lightgray'}}>
<Search
{...this.props}
resource='concepts'
nested
essentialColumns
baseURL={version.url + 'concepts/'}
fixedFilters={{isTable: false, limit: 25}}
searchInputPlaceholder={`Search concepts...`}
noFilters
noNav
onSelectChange={selectedList => this.onSelectChange('concepts', selectedList)}
/>
</div>
<div className='col-md-6 no-right-padding' style={{marginTop: '15px', width: '50%', minWidth: '50%'}}>
<Search
{...this.props}
resource='mappings'
nested
essentialColumns
baseURL={version.url + 'mappings/'}
fixedFilters={{isTable: false, limit: 25}}
searchInputPlaceholder={`Search mappings...`}
noFilters
noNav
onSelectChange={selectedList => this.onSelectChange('mappings', selectedList)}
/>
</div>
</React.Fragment>
}
</div>
)
}
}

export default ResourceReferenceForm;
19 changes: 7 additions & 12 deletions src/components/common/CommonFormDrawer.jsx
@@ -1,21 +1,16 @@
import React from 'react';
import { Drawer } from '@material-ui/core';

const CommonFormDrawer = ({ isOpen, onClose, formComponent }) => {
const CommonFormDrawer = ({ isOpen, onClose, formComponent, size }) => {
const className = 'custom-drawer ' + (size || 'medium')
const [open, setOpen] = React.useState(isOpen);
const onDrawerClose = () => {
setOpen(() => {
onClose()
return false;
})
}

React.useEffect(() => {
setOpen(isOpen)
}, [isOpen])
const onDrawerClose = () => setOpen(() => {
onClose(); return false;
})
React.useEffect(() => setOpen(isOpen), [isOpen])

return (
<Drawer anchor='right' open={open} onClose={onDrawerClose} className="custom-drawer medium">
<Drawer anchor='right' open={open} onClose={onDrawerClose} className={className}>
{ formComponent }
</Drawer>
)
Expand Down
43 changes: 43 additions & 0 deletions src/components/common/ConceptContainersAutocomplete.jsx
@@ -0,0 +1,43 @@
import React from 'react';
import {List as ListIcon, Loyalty as LoyaltyIcon} from '@material-ui/icons';
import { TextField } from '@material-ui/core';
import Autocomplete from '@material-ui/lab/Autocomplete';
import { get } from 'lodash'
import { GREEN } from '../../common/constants';

const ConceptContainersAutocomplete = ({onChange, items, label, id, required, selected}) => {
return (
<Autocomplete
blurOnSelect
getOptionSelected={(option, value) => option.id === get(value, 'id')}
value={selected}
id={id || 'conceptContainer'}
options={items}
getOptionLabel={option => option ? `${option.name} (${option.type})` : ''}
fullWidth
required={required}
renderInput={
params => <TextField {...params} required label={label || "Collection/Source"} variant="outlined" fullWidth />
}
renderOption={
option => (
<React.Fragment>
<span className='flex-vertical-center'>
<span style={{marginRight: '5px'}}>
{
option.type === 'source' ?
<ListIcon fontSize='small' style={{marginTop: '5px', color: GREEN, fontSize: '1rem'}} />:
<LoyaltyIcon fontSize='small' style={{marginTop: '5px', color: GREEN, fontSize: '1rem'}} />
}
</span>
{option.name}
</span>
</React.Fragment>
)
}
onChange={(event, item) => onChange(get(item, 'type') || 'source', item)}
/>
)
}

export default ConceptContainersAutocomplete;

0 comments on commit 880a0ba

Please sign in to comment.