diff --git a/src/common/formatters.ts b/src/common/formatters.ts index 0011c0459..5d29f77e7 100644 --- a/src/common/formatters.ts +++ b/src/common/formatters.ts @@ -71,7 +71,7 @@ export function millisecondsToHMS(valueMS: number): string { } if (valueMS < 1000) { - return subSecondString; + return `${valueMS}ms`; } const duration = moment.duration(valueMS); diff --git a/src/common/test/formatters.spec.ts b/src/common/test/formatters.spec.ts index 718ce5db0..5f209743a 100644 --- a/src/common/test/formatters.spec.ts +++ b/src/common/test/formatters.spec.ts @@ -102,8 +102,8 @@ describe('formatDateLocalTimezone', () => { const millisecondToHMSTestCases: [number, string][] = [ [-1, unknownValueString], [0, zeroSecondsString], - [1, subSecondString], - [999, subSecondString], + [1, '1ms'], + [999, '999ms'], [1000, '1s'], [60000, '1m'], [61000, '1m 1s'], diff --git a/src/common/utils.ts b/src/common/utils.ts index cdf07cb5b..c637e21d1 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -99,3 +99,20 @@ export function sortedObjectKeys( ): ReturnType { return Object.keys(object).sort((a, b) => a.localeCompare(b)); } + +/** + * Helper function for exhaustive checks of discriminated unions. + * https://basarat.gitbooks.io/typescript/docs/types/discriminated-unions.html + */ +export function assertNever( + value: never, + { noThrow }: { noThrow?: boolean } = {} +): never { + if (noThrow) { + return value; + } + + throw new Error( + `Unhandled discriminated union member: ${JSON.stringify(value)}` + ); +} diff --git a/src/components/Launch/LaunchWorkflowForm/BlobInput.tsx b/src/components/Launch/LaunchWorkflowForm/BlobInput.tsx new file mode 100644 index 000000000..0073c287d --- /dev/null +++ b/src/components/Launch/LaunchWorkflowForm/BlobInput.tsx @@ -0,0 +1,126 @@ +import { + FormControl, + FormHelperText, + InputLabel, + MenuItem, + Select, + TextField, + Typography +} from '@material-ui/core'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import { BlobDimensionality } from 'models'; +import * as React from 'react'; +import { + blobFormatHelperText, + blobUriHelperText, + defaultBlobValue +} from './constants'; +import { InputProps } from './types'; +import { getLaunchInputId, isBlobValue } from './utils'; + +const useStyles = makeStyles((theme: Theme) => ({ + dimensionalityInput: { + flex: '1 1 auto', + marginLeft: theme.spacing(2) + }, + formatInput: { + flex: '1 1 auto' + }, + inputContainer: { + borderLeft: `1px solid ${theme.palette.divider}`, + marginTop: theme.spacing(1), + paddingLeft: theme.spacing(1) + }, + metadataContainer: { + display: 'flex', + marginTop: theme.spacing(1), + width: '100%' + } +})); + +/** A micro form for entering the values related to a Blob Literal */ +export const BlobInput: React.FC = props => { + const styles = useStyles(); + const { error, label, name, onChange, value: propValue } = props; + const blobValue = isBlobValue(propValue) ? propValue : defaultBlobValue; + const hasError = !!error; + const helperText = hasError ? error : props.helperText; + + const handleUriChange = ({ + target: { value: uri } + }: React.ChangeEvent) => { + onChange({ + ...blobValue, + uri + }); + }; + + const handleFormatChange = ({ + target: { value: format } + }: React.ChangeEvent) => { + onChange({ + ...blobValue, + format + }); + }; + + const handleDimensionalityChange = ({ + target: { value } + }: React.ChangeEvent<{ value: unknown }>) => { + onChange({ + ...blobValue, + dimensionality: value as BlobDimensionality + }); + }; + + const selectId = getLaunchInputId(`${name}-select`); + + return ( +
+ + {label} + + {helperText} +
+ +
+ + + + Dimensionality + + + +
+
+
+ ); +}; diff --git a/src/components/Launch/LaunchWorkflowForm/LaunchWorkflowForm.tsx b/src/components/Launch/LaunchWorkflowForm/LaunchWorkflowForm.tsx index 7d48ca917..9157d7983 100644 --- a/src/components/Launch/LaunchWorkflowForm/LaunchWorkflowForm.tsx +++ b/src/components/Launch/LaunchWorkflowForm/LaunchWorkflowForm.tsx @@ -16,12 +16,13 @@ import { workflowSortFields } from 'models'; import * as React from 'react'; -import { formStrings } from './constants'; +import { formStrings, unsupportedRequiredInputsString } from './constants'; import { InputValueCacheContext } from './inputValueCache'; import { LaunchWorkflowFormInputs } from './LaunchWorkflowFormInputs'; import { SearchableSelector } from './SearchableSelector'; import { useStyles } from './styles'; import { LaunchWorkflowFormProps } from './types'; +import { UnsupportedRequiredInputsError } from './UnsupportedRequiredInputsError'; import { useLaunchWorkflowFormState } from './useLaunchWorkflowFormState'; import { workflowsToSearchableSelectorOptions } from './utils'; @@ -67,6 +68,11 @@ export const LaunchWorkflowForm: React.FC = props => { state.onSubmit(); }; + const preventSubmit = + submissionState.loading || + !state.inputLoadingState.hasLoaded || + state.unsupportedRequiredInputs.length > 0; + return ( @@ -114,12 +120,18 @@ export const LaunchWorkflowForm: React.FC = props => { {...state.inputLoadingState} >
- + {state.unsupportedRequiredInputs.length > 0 ? ( + + ) : ( + + )}
) : null} @@ -143,10 +155,7 @@ export const LaunchWorkflowForm: React.FC = props => {