Skip to content

Commit 2a39e0e

Browse files
author
Martin Krulis
committed
Adding tags to exercise details and exercise lists. Adjusting appearance of the list to make room for tags (and some refactoring).
1 parent f84167f commit 2a39e0e

File tree

18 files changed

+172
-73
lines changed

18 files changed

+172
-73
lines changed

src/components/Exercises/DifficultyIcon/DifficultyIcon.js

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,35 @@ import PropTypes from 'prop-types';
33
import { FormattedMessage } from 'react-intl';
44
import Icon from '../../icons';
55

6-
const DifficultyIcon = ({ difficulty }) => {
7-
switch (difficulty) {
8-
case 'easy':
9-
return (
10-
<span className="text-success">
11-
<Icon icon={['far', 'smile']} gapRight />
12-
<FormattedMessage id="app.exercises.difficultyIcon.easy" defaultMessage="Easy" />
13-
</span>
14-
);
15-
16-
case 'medium':
17-
case 'moderate':
18-
return (
19-
<span className="text-warning">
20-
<Icon icon={['far', 'meh']} gapRight />
21-
<FormattedMessage id="app.exercises.difficultyIcon.medium" defaultMessage="Medium" />
22-
</span>
23-
);
6+
const difficultyIcons = {
7+
easy: 'smile',
8+
medium: 'meh',
9+
hard: 'frown',
10+
};
2411

25-
case 'hard':
26-
return (
27-
<span className="text-danger">
28-
<Icon icon={['far', 'frown']} gapRight />
29-
<FormattedMessage id="app.exercises.difficultyIcon.hard" defaultMessage="Hard" />
30-
</span>
31-
);
12+
const difficultyClassNames = {
13+
easy: 'text-success',
14+
medium: 'text-warning',
15+
hard: 'text-danger',
16+
};
3217

33-
default:
34-
return null;
35-
}
18+
const difficultyCaptions = {
19+
easy: <FormattedMessage id="app.exercises.difficultyIcon.easy" defaultMessage="Easy" />,
20+
medium: <FormattedMessage id="app.exercises.difficultyIcon.medium" defaultMessage="Medium" />,
21+
hard: <FormattedMessage id="app.exercises.difficultyIcon.hard" defaultMessage="Hard" />,
3622
};
3723

24+
const DifficultyIcon = ({ difficulty }) => (
25+
<span className={difficultyClassNames[difficulty] || 'text-success'}>
26+
<Icon icon={['far', difficultyIcons[difficulty] || 'smile']} smallGapRight />
27+
<small>
28+
{difficultyCaptions[difficulty] || (
29+
<FormattedMessage id="app.exercises.difficultyIcon.unknown" defaultMessage="Unknown" />
30+
)}
31+
</small>
32+
</span>
33+
);
34+
3835
DifficultyIcon.propTypes = {
3936
difficulty: PropTypes.string.isRequired,
4037
};

src/components/Exercises/ExerciseButtons/ExerciseButtons.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ButtonGroup } from 'react-bootstrap';
55
import { LinkContainer } from 'react-router-bootstrap';
66

77
import Button from '../../widgets/FlatButton';
8-
import Icon, { EditIcon } from '../../icons';
8+
import { EditIcon, GroupIcon, LimitsIcon, TestsIcon } from '../../icons';
99
import withLinks from '../../../helpers/withLinks';
1010

1111
const ExerciseButtons = ({
@@ -22,8 +22,8 @@ const ExerciseButtons = ({
2222
<ButtonGroup>
2323
<LinkContainer to={EXERCISE_ASSIGNMENTS_URI_FACTORY(exerciseId)}>
2424
<Button bsStyle="primary" bsSize="sm">
25-
<Icon icon="tasks" gapRight />
26-
<FormattedMessage id="app.exercise.assignments" defaultMessage="Assignments" />
25+
<GroupIcon gapRight />
26+
<FormattedMessage id="app.exercise.assignments" defaultMessage="Assignments in Groups" />
2727
</Button>
2828
</LinkContainer>
2929

@@ -39,7 +39,7 @@ const ExerciseButtons = ({
3939
{permissionHints && permissionHints.viewPipelines && permissionHints.viewScoreConfig && (
4040
<LinkContainer to={EXERCISE_EDIT_CONFIG_URI_FACTORY(exerciseId)}>
4141
<Button bsStyle={permissionHints.setScoreConfig ? 'warning' : 'default'} bsSize="sm">
42-
<EditIcon gapRight />
42+
<TestsIcon gapRight />
4343
<FormattedMessage id="app.exercise.editConfig" defaultMessage="Tests Configuration" />
4444
</Button>
4545
</LinkContainer>
@@ -48,7 +48,7 @@ const ExerciseButtons = ({
4848
{permissionHints && permissionHints.viewLimits && (
4949
<LinkContainer to={EXERCISE_EDIT_LIMITS_URI_FACTORY(exerciseId)}>
5050
<Button bsStyle={permissionHints.setLimits ? 'warning' : 'default'} bsSize="sm">
51-
<EditIcon gapRight />
51+
<LimitsIcon gapRight />
5252
<FormattedMessage id="app.exercise.editLimits" defaultMessage="Tests Limits" />
5353
</Button>
5454
</LinkContainer>

src/components/Exercises/ExerciseDetail/ExerciseDetail.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import { FormattedMessage, FormattedNumber } from 'react-intl';
4-
import { Table } from 'react-bootstrap';
4+
import { Table, Label } from 'react-bootstrap';
55
import { Link } from 'react-router-dom';
66

77
import Box from '../../widgets/Box';
@@ -11,11 +11,12 @@ import DifficultyIcon from '../DifficultyIcon';
1111
import ResourceRenderer from '../../helpers/ResourceRenderer';
1212
import withLinks from '../../../helpers/withLinks';
1313
import UsersNameContainer from '../../../containers/UsersNameContainer';
14-
import Icon, { SuccessOrFailureIcon, UserIcon, VisibleIcon, CodeIcon } from '../../icons';
14+
import Icon, { SuccessOrFailureIcon, UserIcon, VisibleIcon, CodeIcon, TagIcon } from '../../icons';
1515
import { getLocalizedDescription } from '../../../helpers/localizedData';
1616
import { LocalizedExerciseName } from '../../helpers/LocalizedNames';
1717
import EnvironmentsList from '../../helpers/EnvironmentsList';
1818
import Version from '../../widgets/Version/Version';
19+
import { getTagStyle } from '../../../helpers/exercise/tags';
1920

2021
const ExerciseDetail = ({
2122
authorId,
@@ -27,6 +28,7 @@ const ExerciseDetail = ({
2728
forkedFrom = null,
2829
localizedTexts,
2930
runtimeEnvironments,
31+
tags,
3032
isPublic,
3133
isLocked,
3234
locale,
@@ -91,6 +93,22 @@ const ExerciseDetail = ({
9193
</td>
9294
</tr>
9395

96+
<tr>
97+
<td className="text-center shrink-col em-padding-left em-padding-right">
98+
<TagIcon />
99+
</td>
100+
<th className="text-nowrap">
101+
<FormattedMessage id="generic.tags" defaultMessage="Tags" />:
102+
</th>
103+
<td>
104+
{tags.sort().map(tag => (
105+
<Label key={tag} className="tag-margin" style={getTagStyle(tag)}>
106+
{tag}
107+
</Label>
108+
))}
109+
</td>
110+
</tr>
111+
94112
<tr>
95113
<td className="text-center shrink-col em-padding-left em-padding-right">
96114
<Icon icon={['far', 'clock']} />
@@ -180,6 +198,7 @@ ExerciseDetail.propTypes = {
180198
forkedFrom: PropTypes.object,
181199
localizedTexts: PropTypes.array.isRequired,
182200
runtimeEnvironments: PropTypes.array.isRequired,
201+
tags: PropTypes.array.isRequired,
183202
isPublic: PropTypes.bool.isRequired,
184203
isLocked: PropTypes.bool.isRequired,
185204
locale: PropTypes.string.isRequired,

src/components/Exercises/ExercisesListItem/ExercisesListItem.js

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import { FormattedMessage } from 'react-intl';
44
import { LinkContainer } from 'react-router-bootstrap';
55
import { Link } from 'react-router-dom';
6+
import { Label, OverlayTrigger, Tooltip } from 'react-bootstrap';
67

78
import DifficultyIcon from '../DifficultyIcon';
89
import UsersNameContainer from '../../../containers/UsersNameContainer';
@@ -11,10 +12,11 @@ import DeleteExerciseButtonContainer from '../../../containers/DeleteExerciseBut
1112

1213
import { LocalizedExerciseName } from '../../helpers/LocalizedNames';
1314
import EnvironmentsList from '../../helpers/EnvironmentsList';
14-
import { ExercisePrefixIcons, EditIcon } from '../../icons';
15+
import { ExercisePrefixIcons, EditIcon, LimitsIcon, TestsIcon } from '../../icons';
1516
import Button from '../../widgets/FlatButton';
1617
import DateTime from '../../widgets/DateTime';
1718
import AssignExerciseButton from '../../buttons/AssignExerciseButton';
19+
import { getTagStyle } from '../../../helpers/exercise/tags';
1820

1921
import withLinks from '../../../helpers/withLinks';
2022

@@ -24,6 +26,7 @@ const ExercisesListItem = ({
2426
difficulty,
2527
authorId,
2628
runtimeEnvironments,
29+
tags,
2730
groupsIds = [],
2831
localizedTexts,
2932
createdAt,
@@ -46,6 +49,7 @@ const ExercisesListItem = ({
4649
<td className="shrink-col">
4750
<ExercisePrefixIcons id={id} isLocked={isLocked} isBroken={isBroken} />
4851
</td>
52+
4953
<td>
5054
<strong>
5155
{permissionHints.viewDetail ? (
@@ -57,10 +61,21 @@ const ExercisesListItem = ({
5761
)}
5862
</strong>
5963
</td>
64+
6065
<td>
6166
<UsersNameContainer userId={authorId} />
6267
</td>
68+
6369
<td className="small">{runtimeEnvironments && <EnvironmentsList runtimeEnvironments={runtimeEnvironments} />}</td>
70+
71+
<td className="small">
72+
{tags.sort().map(tag => (
73+
<Label key={tag} className="tag-margin" style={getTagStyle(tag)}>
74+
{tag}
75+
</Label>
76+
))}
77+
</td>
78+
6479
{showGroups && (
6580
<td className="small">
6681
{groupsIds.length > 0 ? (
@@ -76,13 +91,16 @@ const ExercisesListItem = ({
7691
)}
7792
</td>
7893
)}
94+
7995
<td className="text-nowrap">
8096
<DifficultyIcon difficulty={difficulty} />
8197
</td>
98+
8299
<td className="text-nowrap">
83100
<DateTime
84101
unixts={createdAt}
85102
showOverlay
103+
showTime={false}
86104
overlayTooltipId={`created-tooltip-${id}`}
87105
customTooltip={
88106
<span>
@@ -102,30 +120,51 @@ const ExercisesListItem = ({
102120
)}
103121
{permissionHints.update && (
104122
<LinkContainer to={EXERCISE_EDIT_URI_FACTORY(id)}>
105-
<Button bsSize="xs" bsStyle="warning">
106-
<EditIcon gapRight />
107-
<FormattedMessage id="app.exercises.listEdit" defaultMessage="Settings" />
108-
</Button>
123+
<OverlayTrigger
124+
placement="bottom"
125+
overlay={
126+
<Tooltip id={`${id}-settings`}>
127+
<FormattedMessage id="app.exercises.listEdit" defaultMessage="Settings" />
128+
</Tooltip>
129+
}>
130+
<Button bsSize="xs" bsStyle="warning">
131+
<EditIcon smallGapLeft smallGapRight />
132+
</Button>
133+
</OverlayTrigger>
109134
</LinkContainer>
110135
)}
111136
{permissionHints.viewPipelines && permissionHints.viewScoreConfig && (
112137
<LinkContainer to={EXERCISE_EDIT_CONFIG_URI_FACTORY(id)}>
113-
<Button bsSize="xs" bsStyle={permissionHints.setScoreConfig ? 'warning' : 'default'}>
114-
<EditIcon gapRight />
115-
<FormattedMessage id="app.exercises.listEditConfig" defaultMessage="Tests" />
116-
</Button>
138+
<OverlayTrigger
139+
placement="bottom"
140+
overlay={
141+
<Tooltip id={`${id}-tests`}>
142+
<FormattedMessage id="app.exercises.listEditConfig" defaultMessage="Tests" />
143+
</Tooltip>
144+
}>
145+
<Button bsSize="xs" bsStyle={permissionHints.setScoreConfig ? 'warning' : 'default'}>
146+
<TestsIcon smallGapLeft smallGapRight />
147+
</Button>
148+
</OverlayTrigger>
117149
</LinkContainer>
118150
)}
119151
{permissionHints.viewLimits && (
120152
<LinkContainer to={EXERCISE_EDIT_LIMITS_URI_FACTORY(id)}>
121-
<Button bsSize="xs" bsStyle={permissionHints.setLimits ? 'warning' : 'default'}>
122-
<EditIcon gapRight />
123-
<FormattedMessage id="app.exercises.listEditLimits" defaultMessage="Limits" />
124-
</Button>
153+
<OverlayTrigger
154+
placement="bottom"
155+
overlay={
156+
<Tooltip id={`${id}-limits`}>
157+
<FormattedMessage id="app.exercises.listEditLimits" defaultMessage="Limits" />
158+
</Tooltip>
159+
}>
160+
<Button bsSize="xs" bsStyle={permissionHints.setLimits ? 'warning' : 'default'}>
161+
<LimitsIcon smallGapLeft smallGapRight />
162+
</Button>
163+
</OverlayTrigger>
125164
</LinkContainer>
126165
)}
127166
{permissionHints.remove && (
128-
<DeleteExerciseButtonContainer id={id} bsSize="xs" resourceless={true} onDeleted={reload} />
167+
<DeleteExerciseButtonContainer id={id} bsSize="xs" resourceless captionAsLabel onDeleted={reload} />
129168
)}
130169
</td>
131170
</tr>
@@ -135,6 +174,7 @@ ExercisesListItem.propTypes = {
135174
id: PropTypes.string.isRequired,
136175
authorId: PropTypes.string.isRequired,
137176
runtimeEnvironments: PropTypes.array.isRequired,
177+
tags: PropTypes.array.isRequired,
138178
groupsIds: PropTypes.array,
139179
name: PropTypes.string.isRequired,
140180
difficulty: PropTypes.string.isRequired,

src/components/buttons/AssignExerciseButton/AssignExerciseButton.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const AssignExerciseButton = ({ isLocked, isBroken, assignExercise, ...props })
1818
);
1919
} else {
2020
return (
21-
<Button onClick={assignExercise} bsSize="xs" className="btn-flat">
21+
<Button onClick={assignExercise} bsSize="xs" className="btn-flat" bsStyle="success">
2222
<SendIcon gapRight />
2323
<FormattedMessage id="app.exercise.assignButton" defaultMessage="Assign" />
2424
</Button>
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import { FormattedMessage } from 'react-intl';
4-
import Button from '../../widgets/FlatButton';
5-
import { DeleteIcon } from '../../icons';
64
import Confirm from '../../forms/Confirm';
5+
import DeleteButtonRaw from './DeleteButtonRaw';
76

87
const ConfirmDeleteButton = ({
98
id,
109
onClick,
11-
disabled,
12-
small = true,
1310
question = (
1411
<FormattedMessage
1512
id="app.deleteButton.confirm"
@@ -19,19 +16,14 @@ const ConfirmDeleteButton = ({
1916
...props
2017
}) => (
2118
<Confirm id={id} onConfirmed={onClick} question={question}>
22-
<Button disabled={disabled || !id} bsStyle="danger" bsSize={small ? 'sm' : undefined} {...props}>
23-
<DeleteIcon gapRight />
24-
<FormattedMessage id="generic.delete" defaultMessage="Delete" />
25-
</Button>
19+
<DeleteButtonRaw id={id} {...props} />
2620
</Confirm>
2721
);
2822

2923
ConfirmDeleteButton.propTypes = {
3024
onClick: PropTypes.func,
3125
id: PropTypes.string,
32-
small: PropTypes.bool,
3326
question: PropTypes.any,
34-
disabled: PropTypes.bool,
3527
};
3628

3729
export default ConfirmDeleteButton;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { FormattedMessage } from 'react-intl';
4+
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
5+
import Button from '../../widgets/FlatButton';
6+
import { DeleteIcon } from '../../icons';
7+
8+
const DeleteButtonRaw = ({ id, disabled, small = true, captionAsLabel = false, ...props }) =>
9+
captionAsLabel ? (
10+
<OverlayTrigger
11+
placement="bottom"
12+
overlay={
13+
<Tooltip id={`${id}-delete`}>
14+
<FormattedMessage id="generic.delete" defaultMessage="Delete" />
15+
</Tooltip>
16+
}>
17+
<Button disabled={disabled || !id} bsStyle="danger" bsSize={small ? 'sm' : undefined} {...props}>
18+
<DeleteIcon smallGapLeft smallGapRight />
19+
</Button>
20+
</OverlayTrigger>
21+
) : (
22+
<Button disabled={disabled || !id} bsStyle="danger" bsSize={small ? 'sm' : undefined} {...props}>
23+
<DeleteIcon gapRight />
24+
<FormattedMessage id="generic.delete" defaultMessage="Delete" />
25+
</Button>
26+
);
27+
28+
DeleteButtonRaw.propTypes = {
29+
id: PropTypes.string,
30+
small: PropTypes.bool,
31+
captionAsLabel: PropTypes.bool,
32+
disabled: PropTypes.bool,
33+
};
34+
35+
export default DeleteButtonRaw;

0 commit comments

Comments
 (0)