Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

Add new design demo in job submission page #5303

Merged
merged 11 commits into from
Feb 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/webportal/config/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const config = (env, argv) => ({
batchRegister: './src/app/user/fabric/batch-register.jsx',
dashboard: './src/app/dashboard/dashboard.component.js',
submit: './src/app/job-submission/job-submission.jsx',
submit_demo: './src/app/job-submission-demo/job-submission.jsx',
submit_v1: './src/app/job/job-submit-v1/job-submit.component.js',
jobList: './src/app/job/job-view/fabric/job-list.jsx',
jobDetail: './src/app/job/job-view/fabric/job-detail.jsx',
Expand Down Expand Up @@ -324,6 +325,10 @@ const config = (env, argv) => ({
filename: 'submit.html',
chunks: ['layout', 'submit'],
}),
generateHtml({
filename: 'submit_demo.html',
chunks: ['layout', 'submit_demo'],
}),
generateHtml({
filename: 'submit_v1.html',
chunks: ['layout', 'submit_v1'],
Expand Down
6 changes: 5 additions & 1 deletion src/webportal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,23 @@
"normalize.css": "^8.0.1",
"office-ui-fabric-react": "^6.189.0",
"papaparse": "^5.2.0",
"prettier": "1.18.2",
"prop-types": "^15.7.2",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"react-monaco-editor": "^0.25.1",
"react-redux": "^7.2.2",
"react-responsive": "^8.0.1",
"react-router-dom": "^5.0.1",
"redux": "^4.0.5",
"redux-saga": "^1.1.3",
"regenerator-runtime": "^0.13.2",
"serve-favicon": "~2.5.0",
"sshpk": "^1.16.1",
"strip-bom-string": "^1.0.0",
"strip-json-comments": "^2.0.1",
"styled-components": "^4.2.0",
"styled-system": "^5.1.5",
"tachyons-sass": "^4.9.5",
"url-join": "^4.0.1",
"util": "~0.10.3",
Expand Down Expand Up @@ -123,7 +128,6 @@
"node-sass": "^4.13.1",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"prettier": "^1.18.2",
Morningssir marked this conversation as resolved.
Show resolved Hide resolved
"raw-loader": "~0.5.1",
"rimraf": "~2.6.2",
"sass-loader": "~6.0.6",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { TextField } from 'office-ui-fabric-react';

import PropTypes from 'prop-types';
import { debounce } from 'lodash';

export const DebouncedTextField = props => {
const { onChange, value } = props;
const [cachedValue, setCachedValue] = useState('');
useEffect(() => setCachedValue(value), [value]);
const debouncedOnChange = useMemo(() => debounce(onChange, 200), [onChange]);

const onChangeWrapper = useCallback(
(e, val) => {
setCachedValue(val);
debouncedOnChange(e, val);
},
[setCachedValue, debouncedOnChange],
);

return (
<TextField {...props} value={cachedValue} onChange={onChangeWrapper} />
);
};

DebouncedTextField.propTypes = {
onChange: PropTypes.func,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import {
IconButton,
Stack,
DetailsList,
CheckboxVisibility,
DetailsListLayoutMode,
CommandBarButton,
SelectionMode,
} from 'office-ui-fabric-react';
import PropTypes from 'prop-types';
import { countBy, isEmpty } from 'lodash';
import React, { useCallback } from 'react';
import { DebouncedTextField } from './debounced-text-field';
import { Flex } from '../../elements';
import theme from '../../theme';

export const KeyValueList = ({
items,
onChange,
isSecret,
keyHeader,
valueHeader,
}) => {
keyHeader = keyHeader || 'Key';
valueHeader = valueHeader || 'Value';

const onAdd = useCallback(() => {
onChange([...items, { key: '', value: '' }]);
}, [onChange, items]);

const onRemove = useCallback(
idx => {
onChange([...items.slice(0, idx), ...items.slice(idx + 1)]);
},
[onChange, items],
);

const onKeyChange = useCallback(
(idx, val) => {
onChange([
...items.slice(0, idx),
{ ...items[idx], key: val },
...items.slice(idx + 1),
]);
},
[onChange, items],
);

const onValueChange = useCallback(
(idx, val) => {
onChange([
...items.slice(0, idx),
{ ...items[idx], value: val },
...items.slice(idx + 1),
]);
},
[onChange, items],
);

const getKey = useCallback((item, idx) => idx, []);

// workaround for fabric's bug
// https://github.com/OfficeDev/office-ui-fabric-react/issues/5280#issuecomment-489619108
// useLayoutEffect(() => {
// dispatchResizeEvent();
// });

const { space } = theme;

const columns = [
{
key: 'key',
name: keyHeader,
// minWidth: 180,
onRender: (item, idx) => {
return (
<DebouncedTextField
errorMessage={item.keyError}
value={item.key}
onChange={(e, val) => onKeyChange(idx, val)}
/>
);
},
},
{
key: 'value',
name: valueHeader,
// minWidth: 180,
onRender: (item, idx) => {
return (
<DebouncedTextField
errorMessage={item.valueError}
value={item.value}
type={isSecret && 'password'}
onChange={(e, val) => onValueChange(idx, val)}
/>
);
},
},
{
key: 'remove',
name: 'Remove',
minWidth: 50,
onRender: (item, idx) => (
<Flex alignItems='baseline' justifyContent='center' height='100%'>
<IconButton
key={`remove-button-${idx}`}
iconProps={{ iconName: 'Delete' }}
onClick={() => onRemove(idx)}
/>
</Flex>
),
},
];

return (
<Stack gap='m'>
<div>
<DetailsList
items={items}
columns={columns}
getKey={getKey}
checkboxVisibility={CheckboxVisibility.hidden}
layoutMode={DetailsListLayoutMode.fixedColumns}
selectionMode={SelectionMode.none}
compact
/>
</div>
<div>
<CommandBarButton
styles={{ root: { padding: space.s1 } }}
iconProps={{ iconName: 'Add' }}
onClick={onAdd}
>
Add
</CommandBarButton>
</div>
</Stack>
);
};

KeyValueList.propTypes = {
items: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
// custom field
isSecret: PropTypes.bool,
keyHeader: PropTypes.string,
valueHeader: PropTypes.string,
};

export const getItemsWithError = items => {
const result = [];
// remove old errors
for (const item of items) {
if (item.keyError || item.valueError) {
result.push({ key: item.key, value: item.value });
} else {
result.push(item);
}
}
// empty key
for (const [idx, item] of result.entries()) {
if (isEmpty(item.key)) {
result[idx] = { ...item, keyError: 'Empty key' };
}
}
// duplicate key
const keyCount = countBy(result, x => x.key);
for (const [idx, item] of result.entries()) {
if (keyCount[item.key] > 1 && isEmpty(item.keyError)) {
result[idx] = { ...item, keyError: 'Duplicated key' };
}
}
return result;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import React, { useCallback, useMemo } from 'react';
import {
Label,
DelayedRender,
AnimationClassNames,
FontSizes,
} from 'office-ui-fabric-react';
import PropTypes from 'prop-types';
import MonacoEditor from '../../../components/monaco-editor';
import { isEmpty, isNil, debounce } from 'lodash';
import theme from '../../theme';

export const MonacoTextField = props => {
const {
value,
onChange,
label,
placeholder,
completionItems,
monacoProps: rawMonacoProps,
monacoRef,
errorMessage,
} = props;
const { colors, space } = theme;
const borderColor = isEmpty(errorMessage)
? colors.neutralTertiary
: colors.errorText;

const debouncedOnChange = useMemo(() => debounce(onChange, 100), [onChange]);
const onChangeWrapper = useCallback(
val => {
if (val === placeholder) {
return;
}
debouncedOnChange(val);
},
[debouncedOnChange],
);

const monacoProps = { ...rawMonacoProps };
const rawEditorDidMount = monacoProps.editorDidMount;
delete monacoProps.editorDidMount;
const editorDidMountCallback = useCallback(
(editor, monaco) => {
editor.onDidFocusEditorText(() => {
const value = editor.getValue();
if (value === placeholder) {
editor.setValue('');
}
});
editor.onDidBlurEditorText(() => {
const value = editor.getValue();
if (isEmpty(value) && !isEmpty(placeholder)) {
editor.setValue(placeholder);
}
});

const value = editor.getValue();
if (isEmpty(value) && !isEmpty(placeholder)) {
editor.setValue(placeholder);
}
if (!isNil(rawEditorDidMount)) {
rawEditorDidMount(editor, monaco);
}
},
[rawEditorDidMount],
);

return (
<div>
{!isNil(label) && <Label>{label}</Label>}
<MonacoEditor
style={{
flex: '1 1 100%',
minHeight: 0,
border: 'solid 1px',
borderColor: borderColor,
paddingTop: space.s1,
}}
completionItems={completionItems}
monacoRef={monacoRef}
monacoProps={{
theme: 'vs',
language: 'plaintext',
options: {
automaticLayout: true,
wordWrap: 'on',
readOnly: false,
defaultEOL: 1,
minimap: { enabled: false },
},
value: value,
onChange: onChangeWrapper,
editorDidMount: editorDidMountCallback,
...monacoProps,
}}
/>
{!isEmpty(errorMessage) && (
<div role='alert'>
<DelayedRender>
<p
className={AnimationClassNames.slideDownIn20}
style={{
fontSize: FontSizes.small,
color: colors.errorText,
margin: 0,
paddingTop: space.s2,
display: 'flex',
alignItems: 'center',
}}
>
<span data-automation-id='error-message'>{errorMessage}</span>
</p>
</DelayedRender>
</div>
)}
</div>
);
};

MonacoTextField.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
placeholder: PropTypes.string,
label: PropTypes.string,
completionItems: PropTypes.array,
monacoProps: PropTypes.object,
monacoRef: PropTypes.object,
errorMessage: PropTypes.string,
};
Loading