Skip to content

Commit

Permalink
Adding filters for environments and tags to paginated exercise list.
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Krulis committed Nov 25, 2019
1 parent 2a39e0e commit 1df1bd2
Show file tree
Hide file tree
Showing 18 changed files with 497 additions and 203 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ const ExercisesListItem = ({
</LinkContainer>
)}
{permissionHints.remove && (
<DeleteExerciseButtonContainer id={id} bsSize="xs" resourceless captionAsLabel onDeleted={reload} />
<DeleteExerciseButtonContainer id={id} bsSize="xs" resourceless captionAsTooltip onDeleted={reload} />
)}
</td>
</tr>
Expand Down
6 changes: 3 additions & 3 deletions src/components/buttons/DeleteButton/DeleteButtonRaw.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { OverlayTrigger, Tooltip } from 'react-bootstrap';
import Button from '../../widgets/FlatButton';
import { DeleteIcon } from '../../icons';

const DeleteButtonRaw = ({ id, disabled, small = true, captionAsLabel = false, ...props }) =>
captionAsLabel ? (
const DeleteButtonRaw = ({ id, disabled, small = true, captionAsTooltip = false, ...props }) =>
captionAsTooltip ? (
<OverlayTrigger
placement="bottom"
overlay={
Expand All @@ -28,7 +28,7 @@ const DeleteButtonRaw = ({ id, disabled, small = true, captionAsLabel = false, .
DeleteButtonRaw.propTypes = {
id: PropTypes.string,
small: PropTypes.bool,
captionAsLabel: PropTypes.bool,
captionAsTooltip: PropTypes.bool,
disabled: PropTypes.bool,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field } from 'redux-form';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import { Grid, Row, Col, OverlayTrigger, Tooltip } from 'react-bootstrap';

import { CheckboxField } from '../Fields';
import Icon, { InfoIcon } from '../../icons';
import { STANDALONE_ENVIRONMENTS } from '../../../helpers/exercise/environments';

const EditEnvironmentList = ({
runtimeEnvironments,
namePrefix = '',
showExclusive = false,
fullWidth = false,
intl: { locale },
}) => (
<Grid fluid>
<Row>
{runtimeEnvironments
.sort((a, b) => a.longName.localeCompare(b.longName, locale))
.map(environment => (
<Col key={environment.id} xs={12} sm={6} md={fullWidth ? 4 : 6} lg={fullWidth ? 3 : 6}>
<Field
name={`${namePrefix}${environment.id}`}
component={CheckboxField}
onOff
label={
<span>
{environment.longName}

<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id={`environment-${environment.id}`}>
{environment.description} {environment.extensions}
</Tooltip>
}>
<InfoIcon gapLeft className="text-primary" timid />
</OverlayTrigger>

{showExclusive && STANDALONE_ENVIRONMENTS.includes(environment.id) && (
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id={`environment-standalone-${environment.id}`}>
<FormattedMessage
id="app.editEnvironmentSimpleForm.exclusiveEnvironment"
defaultMessage="Exclusive runtime environment"
/>
</Tooltip>
}>
<Icon icon={['far', 'star']} smallGapLeft className="text-warning half-opaque" />
</OverlayTrigger>
)}
</span>
}
/>
</Col>
))}
</Row>
</Grid>
);

EditEnvironmentList.propTypes = {
runtimeEnvironments: PropTypes.array,
namePrefix: PropTypes.string,
showExclusive: PropTypes.bool,
fullWidth: PropTypes.bool,
intl: intlShape.isRequired,
};

export default injectIntl(EditEnvironmentList);
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { reduxForm, Field } from 'redux-form';
import { FormattedMessage, FormattedHTMLMessage, intlShape, injectIntl } from 'react-intl';
import { Alert, Grid, Row, Col, OverlayTrigger, Tooltip, Well } from 'react-bootstrap';
import { reduxForm } from 'redux-form';
import { FormattedMessage, FormattedHTMLMessage } from 'react-intl';
import { Alert, Well } from 'react-bootstrap';

import { CheckboxField } from '../Fields';
import EditEnvironmentList from './EditEnvironmentList';
import SubmitButton from '../SubmitButton';
import Button from '../../widgets/FlatButton';
import Icon, { RefreshIcon, InfoIcon } from '../../icons';
import { RefreshIcon } from '../../icons';
import { STANDALONE_ENVIRONMENTS } from '../../../helpers/exercise/environments';
import { getConfigVar } from '../../../helpers/config';

Expand All @@ -25,7 +25,6 @@ class EditEnvironmentSimpleForm extends Component {
invalid,
error,
runtimeEnvironments,
intl: { locale },
} = this.props;

return (
Expand All @@ -42,51 +41,7 @@ class EditEnvironmentSimpleForm extends Component {
</Well>
)}

<Grid fluid>
<Row>
{runtimeEnvironments
.sort((a, b) => a.longName.localeCompare(b.longName, locale))
.map((environment, i) => (
<Col key={i} xs={12} sm={6}>
<Field
name={`${environment.id}`}
component={CheckboxField}
onOff
label={
<span>
{environment.longName}

<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id={`environment-${environment.id}`}>
{environment.description} {environment.extensions}
</Tooltip>
}>
<InfoIcon gapLeft className="text-primary" timid />
</OverlayTrigger>

{STANDALONE_ENVIRONMENTS.includes(environment.id) && (
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id={`environment-standalone-${environment.id}`}>
<FormattedMessage
id="app.editEnvironmentSimpleForm.exclusiveEnvironment"
defaultMessage="Exclusive runtime environment"
/>
</Tooltip>
}>
<Icon icon={['far', 'star']} smallGapLeft className="text-warning half-opaque" />
</OverlayTrigger>
)}
</span>
}
/>
</Col>
))}
</Row>
</Grid>
<EditEnvironmentList runtimeEnvironments={runtimeEnvironments} showExclusive />

<hr />

Expand Down Expand Up @@ -146,7 +101,6 @@ EditEnvironmentSimpleForm.propTypes = {
invalid: PropTypes.bool,
error: PropTypes.any,
runtimeEnvironments: PropTypes.array,
intl: intlShape.isRequired,
};

const validate = (formData, { runtimeEnvironments }) => {
Expand Down Expand Up @@ -188,4 +142,4 @@ export default reduxForm({
enableReinitialize: true,
keepDirtyOnReinitialize: false,
validate,
})(injectIntl(EditEnvironmentSimpleForm));
})(EditEnvironmentSimpleForm);
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import EnvironmentsListItem from '../../helpers/EnvironmentsList/EnvironmentsListItem';
import { EMPTY_ARRAY } from '../../../helpers/common';
import Button from '../../widgets/FlatButton';
import Icon from '../../icons';
import Icon, { ExpandCollapseIcon } from '../../icons';
import { SelectField, ExpandingInputFilesField, ExpandingSelectField } from '../Fields';
import Confirm from '../../forms/Confirm';
import { ENV_JAVA_ID } from '../../../helpers/exercise/environments';
Expand Down Expand Up @@ -76,7 +76,7 @@ class EditExerciseSimpleConfigTestCompilation extends Component {
(this.state.compilationOpen === null && this.hasCompilationExtraFiles()) ? (
<Well>
<h4 className="compilation-close" onClick={this.compilationClose}>
<Icon icon={['far', 'minus-square']} gapRight />
<ExpandCollapseIcon isOpen={true} gapRight />
<FormattedMessage
id="app.editExerciseSimpleConfigTests.compilationTitle"
defaultMessage="Compilation/Execution"
Expand Down Expand Up @@ -211,7 +211,7 @@ class EditExerciseSimpleConfigTestCompilation extends Component {
</Well>
) : (
<div className="text-muted compilation-open" onClick={this.compilationOpen}>
<Icon icon={['far', 'plus-square']} gapRight />
<ExpandCollapseIcon isOpen={false} gapRight />
<FormattedMessage
id="app.editExerciseSimpleConfigTests.compilationTitle"
defaultMessage="Compilation/Execution"
Expand Down
67 changes: 67 additions & 0 deletions src/components/forms/Fields/TagsSelectorField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { ControlLabel, Label } from 'react-bootstrap';
import { defaultMemoize } from 'reselect';
import classnames from 'classnames';

import { AddIcon, CloseIcon } from '../../icons';
import { getTagStyle } from '../../../helpers/exercise/tags';

const activeTagsIndex = defaultMemoize(fields => {
const res = {};
fields.forEach((_, index) => (res[fields.get(index)] = index));
return res;
});

const TagsSelectorField = ({ tags = [], fields, label = null }) => {
const active = activeTagsIndex(fields);

return (
<React.Fragment>
{Boolean(label) && <ControlLabel>{label}</ControlLabel>}
<div className="larger">
{tags.sort().map(tag => (
<Label
key={tag}
bsSize="lg"
style={getTagStyle(tag)}
className={classnames({
'tag-margin': true,
'halfem-padding': true,
timid: active[tag] === undefined,
clickable: true,
})}
onClick={() => (active[tag] === undefined ? fields.push(tag) : fields.remove(active[tag]))}>
{tag}
{active[tag] === undefined ? <AddIcon gapLeft /> : <CloseIcon gapLeft />}
</Label>
))}
</div>
</React.Fragment>
);
};

TagsSelectorField.propTypes = {
tags: PropTypes.array,
fields: PropTypes.object.isRequired,
meta: PropTypes.shape({
active: PropTypes.bool,
dirty: PropTypes.bool,
error: PropTypes.any,
warning: PropTypes.any,
}).isRequired,
label: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
PropTypes.shape({ type: PropTypes.oneOf([FormattedMessage]) }),
]),
noItems: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
PropTypes.shape({ type: PropTypes.oneOf([FormattedMessage]) }),
]),
validateEach: PropTypes.func,
};

export default TagsSelectorField;
1 change: 1 addition & 0 deletions src/components/forms/Fields/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export { default as TextField } from './TextField';
export { default as ExpandingTextField } from './ExpandingTextField';
export { default as ExpandingSelectField } from './ExpandingSelectField';
export { default as ExpandingInputFilesField } from './ExpandingInputFilesField';
export { default as TagsSelectorField } from './TagsSelectorField';
Loading

0 comments on commit 1df1bd2

Please sign in to comment.