Skip to content

Commit 1df1bd2

Browse files
author
Martin Krulis
committed
Adding filters for environments and tags to paginated exercise list.
1 parent 2a39e0e commit 1df1bd2

File tree

18 files changed

+497
-203
lines changed

18 files changed

+497
-203
lines changed

src/components/Exercises/ExercisesListItem/ExercisesListItem.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ const ExercisesListItem = ({
164164
</LinkContainer>
165165
)}
166166
{permissionHints.remove && (
167-
<DeleteExerciseButtonContainer id={id} bsSize="xs" resourceless captionAsLabel onDeleted={reload} />
167+
<DeleteExerciseButtonContainer id={id} bsSize="xs" resourceless captionAsTooltip onDeleted={reload} />
168168
)}
169169
</td>
170170
</tr>

src/components/buttons/DeleteButton/DeleteButtonRaw.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { OverlayTrigger, Tooltip } from 'react-bootstrap';
55
import Button from '../../widgets/FlatButton';
66
import { DeleteIcon } from '../../icons';
77

8-
const DeleteButtonRaw = ({ id, disabled, small = true, captionAsLabel = false, ...props }) =>
9-
captionAsLabel ? (
8+
const DeleteButtonRaw = ({ id, disabled, small = true, captionAsTooltip = false, ...props }) =>
9+
captionAsTooltip ? (
1010
<OverlayTrigger
1111
placement="bottom"
1212
overlay={
@@ -28,7 +28,7 @@ const DeleteButtonRaw = ({ id, disabled, small = true, captionAsLabel = false, .
2828
DeleteButtonRaw.propTypes = {
2929
id: PropTypes.string,
3030
small: PropTypes.bool,
31-
captionAsLabel: PropTypes.bool,
31+
captionAsTooltip: PropTypes.bool,
3232
disabled: PropTypes.bool,
3333
};
3434

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { Field } from 'redux-form';
4+
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
5+
import { Grid, Row, Col, OverlayTrigger, Tooltip } from 'react-bootstrap';
6+
7+
import { CheckboxField } from '../Fields';
8+
import Icon, { InfoIcon } from '../../icons';
9+
import { STANDALONE_ENVIRONMENTS } from '../../../helpers/exercise/environments';
10+
11+
const EditEnvironmentList = ({
12+
runtimeEnvironments,
13+
namePrefix = '',
14+
showExclusive = false,
15+
fullWidth = false,
16+
intl: { locale },
17+
}) => (
18+
<Grid fluid>
19+
<Row>
20+
{runtimeEnvironments
21+
.sort((a, b) => a.longName.localeCompare(b.longName, locale))
22+
.map(environment => (
23+
<Col key={environment.id} xs={12} sm={6} md={fullWidth ? 4 : 6} lg={fullWidth ? 3 : 6}>
24+
<Field
25+
name={`${namePrefix}${environment.id}`}
26+
component={CheckboxField}
27+
onOff
28+
label={
29+
<span>
30+
{environment.longName}
31+
32+
<OverlayTrigger
33+
placement="bottom"
34+
overlay={
35+
<Tooltip id={`environment-${environment.id}`}>
36+
{environment.description} {environment.extensions}
37+
</Tooltip>
38+
}>
39+
<InfoIcon gapLeft className="text-primary" timid />
40+
</OverlayTrigger>
41+
42+
{showExclusive && STANDALONE_ENVIRONMENTS.includes(environment.id) && (
43+
<OverlayTrigger
44+
placement="bottom"
45+
overlay={
46+
<Tooltip id={`environment-standalone-${environment.id}`}>
47+
<FormattedMessage
48+
id="app.editEnvironmentSimpleForm.exclusiveEnvironment"
49+
defaultMessage="Exclusive runtime environment"
50+
/>
51+
</Tooltip>
52+
}>
53+
<Icon icon={['far', 'star']} smallGapLeft className="text-warning half-opaque" />
54+
</OverlayTrigger>
55+
)}
56+
</span>
57+
}
58+
/>
59+
</Col>
60+
))}
61+
</Row>
62+
</Grid>
63+
);
64+
65+
EditEnvironmentList.propTypes = {
66+
runtimeEnvironments: PropTypes.array,
67+
namePrefix: PropTypes.string,
68+
showExclusive: PropTypes.bool,
69+
fullWidth: PropTypes.bool,
70+
intl: intlShape.isRequired,
71+
};
72+
73+
export default injectIntl(EditEnvironmentList);

src/components/forms/EditEnvironmentSimpleForm/EditEnvironmentSimpleForm.js

Lines changed: 7 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import React, { Component } from 'react';
22
import PropTypes from 'prop-types';
3-
import { reduxForm, Field } from 'redux-form';
4-
import { FormattedMessage, FormattedHTMLMessage, intlShape, injectIntl } from 'react-intl';
5-
import { Alert, Grid, Row, Col, OverlayTrigger, Tooltip, Well } from 'react-bootstrap';
3+
import { reduxForm } from 'redux-form';
4+
import { FormattedMessage, FormattedHTMLMessage } from 'react-intl';
5+
import { Alert, Well } from 'react-bootstrap';
66

7-
import { CheckboxField } from '../Fields';
7+
import EditEnvironmentList from './EditEnvironmentList';
88
import SubmitButton from '../SubmitButton';
99
import Button from '../../widgets/FlatButton';
10-
import Icon, { RefreshIcon, InfoIcon } from '../../icons';
10+
import { RefreshIcon } from '../../icons';
1111
import { STANDALONE_ENVIRONMENTS } from '../../../helpers/exercise/environments';
1212
import { getConfigVar } from '../../../helpers/config';
1313

@@ -25,7 +25,6 @@ class EditEnvironmentSimpleForm extends Component {
2525
invalid,
2626
error,
2727
runtimeEnvironments,
28-
intl: { locale },
2928
} = this.props;
3029

3130
return (
@@ -42,51 +41,7 @@ class EditEnvironmentSimpleForm extends Component {
4241
</Well>
4342
)}
4443

45-
<Grid fluid>
46-
<Row>
47-
{runtimeEnvironments
48-
.sort((a, b) => a.longName.localeCompare(b.longName, locale))
49-
.map((environment, i) => (
50-
<Col key={i} xs={12} sm={6}>
51-
<Field
52-
name={`${environment.id}`}
53-
component={CheckboxField}
54-
onOff
55-
label={
56-
<span>
57-
{environment.longName}
58-
59-
<OverlayTrigger
60-
placement="bottom"
61-
overlay={
62-
<Tooltip id={`environment-${environment.id}`}>
63-
{environment.description} {environment.extensions}
64-
</Tooltip>
65-
}>
66-
<InfoIcon gapLeft className="text-primary" timid />
67-
</OverlayTrigger>
68-
69-
{STANDALONE_ENVIRONMENTS.includes(environment.id) && (
70-
<OverlayTrigger
71-
placement="bottom"
72-
overlay={
73-
<Tooltip id={`environment-standalone-${environment.id}`}>
74-
<FormattedMessage
75-
id="app.editEnvironmentSimpleForm.exclusiveEnvironment"
76-
defaultMessage="Exclusive runtime environment"
77-
/>
78-
</Tooltip>
79-
}>
80-
<Icon icon={['far', 'star']} smallGapLeft className="text-warning half-opaque" />
81-
</OverlayTrigger>
82-
)}
83-
</span>
84-
}
85-
/>
86-
</Col>
87-
))}
88-
</Row>
89-
</Grid>
44+
<EditEnvironmentList runtimeEnvironments={runtimeEnvironments} showExclusive />
9045

9146
<hr />
9247

@@ -146,7 +101,6 @@ EditEnvironmentSimpleForm.propTypes = {
146101
invalid: PropTypes.bool,
147102
error: PropTypes.any,
148103
runtimeEnvironments: PropTypes.array,
149-
intl: intlShape.isRequired,
150104
};
151105

152106
const validate = (formData, { runtimeEnvironments }) => {
@@ -188,4 +142,4 @@ export default reduxForm({
188142
enableReinitialize: true,
189143
keepDirtyOnReinitialize: false,
190144
validate,
191-
})(injectIntl(EditEnvironmentSimpleForm));
145+
})(EditEnvironmentSimpleForm);

src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigTestCompilation.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
77
import EnvironmentsListItem from '../../helpers/EnvironmentsList/EnvironmentsListItem';
88
import { EMPTY_ARRAY } from '../../../helpers/common';
99
import Button from '../../widgets/FlatButton';
10-
import Icon from '../../icons';
10+
import Icon, { ExpandCollapseIcon } from '../../icons';
1111
import { SelectField, ExpandingInputFilesField, ExpandingSelectField } from '../Fields';
1212
import Confirm from '../../forms/Confirm';
1313
import { ENV_JAVA_ID } from '../../../helpers/exercise/environments';
@@ -76,7 +76,7 @@ class EditExerciseSimpleConfigTestCompilation extends Component {
7676
(this.state.compilationOpen === null && this.hasCompilationExtraFiles()) ? (
7777
<Well>
7878
<h4 className="compilation-close" onClick={this.compilationClose}>
79-
<Icon icon={['far', 'minus-square']} gapRight />
79+
<ExpandCollapseIcon isOpen={true} gapRight />
8080
<FormattedMessage
8181
id="app.editExerciseSimpleConfigTests.compilationTitle"
8282
defaultMessage="Compilation/Execution"
@@ -211,7 +211,7 @@ class EditExerciseSimpleConfigTestCompilation extends Component {
211211
</Well>
212212
) : (
213213
<div className="text-muted compilation-open" onClick={this.compilationOpen}>
214-
<Icon icon={['far', 'plus-square']} gapRight />
214+
<ExpandCollapseIcon isOpen={false} gapRight />
215215
<FormattedMessage
216216
id="app.editExerciseSimpleConfigTests.compilationTitle"
217217
defaultMessage="Compilation/Execution"
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { FormattedMessage } from 'react-intl';
4+
import { ControlLabel, Label } from 'react-bootstrap';
5+
import { defaultMemoize } from 'reselect';
6+
import classnames from 'classnames';
7+
8+
import { AddIcon, CloseIcon } from '../../icons';
9+
import { getTagStyle } from '../../../helpers/exercise/tags';
10+
11+
const activeTagsIndex = defaultMemoize(fields => {
12+
const res = {};
13+
fields.forEach((_, index) => (res[fields.get(index)] = index));
14+
return res;
15+
});
16+
17+
const TagsSelectorField = ({ tags = [], fields, label = null }) => {
18+
const active = activeTagsIndex(fields);
19+
20+
return (
21+
<React.Fragment>
22+
{Boolean(label) && <ControlLabel>{label}</ControlLabel>}
23+
<div className="larger">
24+
{tags.sort().map(tag => (
25+
<Label
26+
key={tag}
27+
bsSize="lg"
28+
style={getTagStyle(tag)}
29+
className={classnames({
30+
'tag-margin': true,
31+
'halfem-padding': true,
32+
timid: active[tag] === undefined,
33+
clickable: true,
34+
})}
35+
onClick={() => (active[tag] === undefined ? fields.push(tag) : fields.remove(active[tag]))}>
36+
{tag}
37+
{active[tag] === undefined ? <AddIcon gapLeft /> : <CloseIcon gapLeft />}
38+
</Label>
39+
))}
40+
</div>
41+
</React.Fragment>
42+
);
43+
};
44+
45+
TagsSelectorField.propTypes = {
46+
tags: PropTypes.array,
47+
fields: PropTypes.object.isRequired,
48+
meta: PropTypes.shape({
49+
active: PropTypes.bool,
50+
dirty: PropTypes.bool,
51+
error: PropTypes.any,
52+
warning: PropTypes.any,
53+
}).isRequired,
54+
label: PropTypes.oneOfType([
55+
PropTypes.string,
56+
PropTypes.element,
57+
PropTypes.shape({ type: PropTypes.oneOf([FormattedMessage]) }),
58+
]),
59+
noItems: PropTypes.oneOfType([
60+
PropTypes.string,
61+
PropTypes.element,
62+
PropTypes.shape({ type: PropTypes.oneOf([FormattedMessage]) }),
63+
]),
64+
validateEach: PropTypes.func,
65+
};
66+
67+
export default TagsSelectorField;

src/components/forms/Fields/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export { default as TextField } from './TextField';
2222
export { default as ExpandingTextField } from './ExpandingTextField';
2323
export { default as ExpandingSelectField } from './ExpandingSelectField';
2424
export { default as ExpandingInputFilesField } from './ExpandingInputFilesField';
25+
export { default as TagsSelectorField } from './TagsSelectorField';

0 commit comments

Comments
 (0)