-
Notifications
You must be signed in to change notification settings - Fork 179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pd file detail form edit #1673
Pd file detail form edit #1673
Changes from all commits
c57872f
db9d6a2
de4b90a
91b684d
2339dde
a3c4e57
4955d20
7c83585
28af4f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,55 @@ | ||
// @flow | ||
import * as React from 'react' | ||
import {FormGroup, InputField, InstrumentGroup} from '@opentrons/components' | ||
import type {FilePageFields} from '../file-data' | ||
import { | ||
FormGroup, | ||
InputField, | ||
InstrumentGroup, | ||
OutlineButton | ||
} from '@opentrons/components' | ||
import type {FileMetadataFields} from '../file-data' | ||
import type {FormConnector} from '../utils' | ||
|
||
import styles from './FilePage.css' | ||
import formStyles from '../components/forms.css' | ||
|
||
type Props = { | ||
formConnector: FormConnector<FilePageFields>, | ||
instruments: React.ElementProps<typeof InstrumentGroup> | ||
export type FilePageProps = { | ||
formConnector: FormConnector<FileMetadataFields>, | ||
isFormAltered: boolean, | ||
instruments: React.ElementProps<typeof InstrumentGroup>, | ||
saveFileMetadata: () => void | ||
} | ||
|
||
export default function FilePage (props: Props) { | ||
const {formConnector, instruments} = props | ||
const FilePage = ({formConnector, isFormAltered, instruments, saveFileMetadata}: FilePageProps) => { | ||
const handleSubmit = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be a good part of a generic |
||
// blur focused field on submit | ||
if (document && document.activeElement) document.activeElement.blur() | ||
saveFileMetadata() | ||
} | ||
return ( | ||
<div className={styles.file_page}> | ||
<section> | ||
<h2> | ||
Information | ||
</h2> | ||
<form onSubmit={handleSubmit}> | ||
<div className={formStyles.row_wrapper}> | ||
<FormGroup label='Protocol Name:' className={formStyles.column_1_2}> | ||
<InputField placeholder='Untitled' {...formConnector('name')} /> | ||
</FormGroup> | ||
|
||
<div className={formStyles.row_wrapper}> | ||
<FormGroup label='Protocol Name:' className={formStyles.column_1_2}> | ||
<InputField placeholder='Untitled' {...formConnector('name')} /> | ||
</FormGroup> | ||
<FormGroup label='Organization/Author:' className={formStyles.column_1_2}> | ||
<InputField {...formConnector('author')} /> | ||
</FormGroup> | ||
</div> | ||
|
||
<FormGroup label='Organization/Author:' className={formStyles.column_1_2}> | ||
<InputField {...formConnector('author')} /> | ||
<FormGroup label='Description:'> | ||
<InputField {...formConnector('description')}/> | ||
</FormGroup> | ||
</div> | ||
|
||
<FormGroup label='Description:'> | ||
<InputField {...formConnector('description')}/> | ||
</FormGroup> | ||
<div className={styles.button_row}> | ||
<OutlineButton type="submit" className={styles.update_button} disabled={!isFormAltered}> | ||
{isFormAltered ? 'UPDATE' : 'UPDATED'} | ||
</OutlineButton> | ||
</div> | ||
</form> | ||
</section> | ||
|
||
<section> | ||
|
@@ -45,3 +61,5 @@ export default function FilePage (props: Props) { | |
</div> | ||
) | ||
} | ||
|
||
export default FilePage |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,60 @@ | ||
// @flow | ||
import * as React from 'react' | ||
import type {Dispatch} from 'redux' | ||
import {connect} from 'react-redux' | ||
import type {BaseState} from '../types' | ||
|
||
import FilePage from '../components/FilePage' | ||
|
||
import type {FilePageProps} from '../components/FilePage' | ||
import {actions, selectors} from '../file-data' | ||
import type {FilePageFields} from '../file-data' | ||
import type {FileMetadataFields} from '../file-data' | ||
import {formConnectorFactory, type FormConnector} from '../utils' | ||
|
||
export default connect(mapStateToProps, null, mergeProps)(FilePage) | ||
|
||
type Props = React.ElementProps<typeof FilePage> | ||
type StateProps = { | ||
instruments: $PropertyType<Props, 'instruments'>, | ||
type SP = { | ||
instruments: $PropertyType<FilePageProps, 'instruments'>, | ||
isFormAltered: boolean, | ||
_values: {[string]: string} | ||
} | ||
|
||
function mapStateToProps (state: BaseState): StateProps { | ||
const formValues = selectors.fileFormValues(state) | ||
const pipetteData = selectors.pipettesForInstrumentGroup(state) | ||
type DP = { | ||
_updateFileMetadataFields: typeof actions.updateFileMetadataFields, | ||
_saveFileMetadata: ({[string]: string}) => void | ||
} | ||
|
||
const mapStateToProps = (state: BaseState): SP => { | ||
const pipetteData = selectors.pipettesForInstrumentGroup(state) | ||
return { | ||
_values: formValues, | ||
_values: selectors.fileFormValues(state), | ||
isFormAltered: selectors.isUnsavedMetadatFormAltered(state), | ||
instruments: { | ||
left: pipetteData.find(i => i.mount === 'left'), | ||
right: pipetteData.find(i => i.mount === 'right') | ||
} | ||
} | ||
} | ||
|
||
function mergeProps ( | ||
stateProps: StateProps, | ||
dispatchProps: {dispatch: Dispatch<*>} | ||
): Props { | ||
const {instruments, _values} = stateProps | ||
const {dispatch} = dispatchProps | ||
const mapDispatchToProps = { | ||
_updateFileMetadataFields: actions.updateFileMetadataFields, | ||
_saveFileMetadata: actions.saveFileMetadata | ||
} | ||
|
||
const mergeProps = ( | ||
{instruments, isFormAltered, _values}: SP, | ||
{_updateFileMetadataFields, _saveFileMetadata}: DP | ||
): FilePageProps => { | ||
const onChange = (accessor) => (e: SyntheticInputEvent<*>) => { | ||
if (accessor === 'name' || accessor === 'description' || accessor === 'author') { | ||
dispatch(actions.updateFileFields({[accessor]: e.target.value})) | ||
_updateFileMetadataFields({[accessor]: e.target.value}) | ||
} else { | ||
console.warn('Invalid accessor in ConnectedFilePage:', accessor) | ||
} | ||
} | ||
|
||
const formConnector: FormConnector<FilePageFields> = formConnectorFactory(onChange, _values) | ||
const formConnector: FormConnector<FileMetadataFields> = formConnectorFactory(onChange, _values) | ||
|
||
return { | ||
formConnector, | ||
instruments | ||
isFormAltered, | ||
instruments, | ||
saveFileMetadata: () => _saveFileMetadata(_values) | ||
} | ||
} | ||
|
||
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(FilePage) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
// @flow | ||
import _ from 'lodash' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We try to import specifically what we're using from import isEqual from 'lodash/isEqual' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch! |
||
import {createSelector} from 'reselect' | ||
import type {BaseState} from '../../types' | ||
import type {RootState} from '../reducers' | ||
|
@@ -7,5 +8,16 @@ export const rootSelector = (state: BaseState): RootState => state.fileData | |
|
||
export const fileFormValues = createSelector( | ||
rootSelector, | ||
state => state.metadataFields | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a bug with this PR where if you change the form fields, do not click UPDATE, and then download the JSON file with the "Export" button, then the file has the "unsaved" name/description not the "saved" one. It happens because this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll switch |
||
state => state.unsavedMetadataForm | ||
) | ||
|
||
export const isUnsavedMetadatFormAltered = createSelector( | ||
rootSelector, | ||
state => !_.isEqual(state.unsavedMetadataForm, state.fileMetadata) | ||
) | ||
|
||
export const protocolName = createSelector(rootSelector, state => state.fileMetadata.name) | ||
export const fileMetadata = createSelector( | ||
rootSelector, | ||
state => state.fileMetadata | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, why the switch to an anonymous arrow function? Also, personally not a huge fan of object destructuring inside function parameters (especially when flow gets involved).
Both of these are highly personal readability preferences, so this comment shouldn't be construed as a change request. But figured I'd bring it up because we were chatting about this stuff earlier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree about avoiding destructuring inside fn params, especially of
props
. We're pretty consistent about that convention across all our components