Skip to content

Commit

Permalink
Adding tags to exercise details and exercise lists. Adjusting appeara…
Browse files Browse the repository at this point in the history
…nce of the list to make room for tags (and some refactoring).
  • Loading branch information
Martin Krulis committed Nov 25, 2019
1 parent f84167f commit 2a39e0e
Show file tree
Hide file tree
Showing 18 changed files with 172 additions and 73 deletions.
53 changes: 25 additions & 28 deletions src/components/Exercises/DifficultyIcon/DifficultyIcon.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,35 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Icon from '../../icons';

const DifficultyIcon = ({ difficulty }) => {
switch (difficulty) {
case 'easy':
return (
<span className="text-success">
<Icon icon={['far', 'smile']} gapRight />
<FormattedMessage id="app.exercises.difficultyIcon.easy" defaultMessage="Easy" />
</span>
);

case 'medium':
case 'moderate':
return (
<span className="text-warning">
<Icon icon={['far', 'meh']} gapRight />
<FormattedMessage id="app.exercises.difficultyIcon.medium" defaultMessage="Medium" />
</span>
);
const difficultyIcons = {
easy: 'smile',
medium: 'meh',
hard: 'frown',
};

case 'hard':
return (
<span className="text-danger">
<Icon icon={['far', 'frown']} gapRight />
<FormattedMessage id="app.exercises.difficultyIcon.hard" defaultMessage="Hard" />
</span>
);
const difficultyClassNames = {
easy: 'text-success',
medium: 'text-warning',
hard: 'text-danger',
};

default:
return null;
}
const difficultyCaptions = {
easy: <FormattedMessage id="app.exercises.difficultyIcon.easy" defaultMessage="Easy" />,
medium: <FormattedMessage id="app.exercises.difficultyIcon.medium" defaultMessage="Medium" />,
hard: <FormattedMessage id="app.exercises.difficultyIcon.hard" defaultMessage="Hard" />,
};

const DifficultyIcon = ({ difficulty }) => (
<span className={difficultyClassNames[difficulty] || 'text-success'}>
<Icon icon={['far', difficultyIcons[difficulty] || 'smile']} smallGapRight />
<small>
{difficultyCaptions[difficulty] || (
<FormattedMessage id="app.exercises.difficultyIcon.unknown" defaultMessage="Unknown" />
)}
</small>
</span>
);

DifficultyIcon.propTypes = {
difficulty: PropTypes.string.isRequired,
};
Expand Down
10 changes: 5 additions & 5 deletions src/components/Exercises/ExerciseButtons/ExerciseButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ButtonGroup } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';

import Button from '../../widgets/FlatButton';
import Icon, { EditIcon } from '../../icons';
import { EditIcon, GroupIcon, LimitsIcon, TestsIcon } from '../../icons';
import withLinks from '../../../helpers/withLinks';

const ExerciseButtons = ({
Expand All @@ -22,8 +22,8 @@ const ExerciseButtons = ({
<ButtonGroup>
<LinkContainer to={EXERCISE_ASSIGNMENTS_URI_FACTORY(exerciseId)}>
<Button bsStyle="primary" bsSize="sm">
<Icon icon="tasks" gapRight />
<FormattedMessage id="app.exercise.assignments" defaultMessage="Assignments" />
<GroupIcon gapRight />
<FormattedMessage id="app.exercise.assignments" defaultMessage="Assignments in Groups" />
</Button>
</LinkContainer>

Expand All @@ -39,7 +39,7 @@ const ExerciseButtons = ({
{permissionHints && permissionHints.viewPipelines && permissionHints.viewScoreConfig && (
<LinkContainer to={EXERCISE_EDIT_CONFIG_URI_FACTORY(exerciseId)}>
<Button bsStyle={permissionHints.setScoreConfig ? 'warning' : 'default'} bsSize="sm">
<EditIcon gapRight />
<TestsIcon gapRight />
<FormattedMessage id="app.exercise.editConfig" defaultMessage="Tests Configuration" />
</Button>
</LinkContainer>
Expand All @@ -48,7 +48,7 @@ const ExerciseButtons = ({
{permissionHints && permissionHints.viewLimits && (
<LinkContainer to={EXERCISE_EDIT_LIMITS_URI_FACTORY(exerciseId)}>
<Button bsStyle={permissionHints.setLimits ? 'warning' : 'default'} bsSize="sm">
<EditIcon gapRight />
<LimitsIcon gapRight />
<FormattedMessage id="app.exercise.editLimits" defaultMessage="Tests Limits" />
</Button>
</LinkContainer>
Expand Down
23 changes: 21 additions & 2 deletions src/components/Exercises/ExerciseDetail/ExerciseDetail.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, FormattedNumber } from 'react-intl';
import { Table } from 'react-bootstrap';
import { Table, Label } from 'react-bootstrap';
import { Link } from 'react-router-dom';

import Box from '../../widgets/Box';
Expand All @@ -11,11 +11,12 @@ import DifficultyIcon from '../DifficultyIcon';
import ResourceRenderer from '../../helpers/ResourceRenderer';
import withLinks from '../../../helpers/withLinks';
import UsersNameContainer from '../../../containers/UsersNameContainer';
import Icon, { SuccessOrFailureIcon, UserIcon, VisibleIcon, CodeIcon } from '../../icons';
import Icon, { SuccessOrFailureIcon, UserIcon, VisibleIcon, CodeIcon, TagIcon } from '../../icons';
import { getLocalizedDescription } from '../../../helpers/localizedData';
import { LocalizedExerciseName } from '../../helpers/LocalizedNames';
import EnvironmentsList from '../../helpers/EnvironmentsList';
import Version from '../../widgets/Version/Version';
import { getTagStyle } from '../../../helpers/exercise/tags';

const ExerciseDetail = ({
authorId,
Expand All @@ -27,6 +28,7 @@ const ExerciseDetail = ({
forkedFrom = null,
localizedTexts,
runtimeEnvironments,
tags,
isPublic,
isLocked,
locale,
Expand Down Expand Up @@ -91,6 +93,22 @@ const ExerciseDetail = ({
</td>
</tr>

<tr>
<td className="text-center shrink-col em-padding-left em-padding-right">
<TagIcon />
</td>
<th className="text-nowrap">
<FormattedMessage id="generic.tags" defaultMessage="Tags" />:
</th>
<td>
{tags.sort().map(tag => (
<Label key={tag} className="tag-margin" style={getTagStyle(tag)}>
{tag}
</Label>
))}
</td>
</tr>

<tr>
<td className="text-center shrink-col em-padding-left em-padding-right">
<Icon icon={['far', 'clock']} />
Expand Down Expand Up @@ -180,6 +198,7 @@ ExerciseDetail.propTypes = {
forkedFrom: PropTypes.object,
localizedTexts: PropTypes.array.isRequired,
runtimeEnvironments: PropTypes.array.isRequired,
tags: PropTypes.array.isRequired,
isPublic: PropTypes.bool.isRequired,
isLocked: PropTypes.bool.isRequired,
locale: PropTypes.string.isRequired,
Expand Down
68 changes: 54 additions & 14 deletions src/components/Exercises/ExercisesListItem/ExercisesListItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { LinkContainer } from 'react-router-bootstrap';
import { Link } from 'react-router-dom';
import { Label, OverlayTrigger, Tooltip } from 'react-bootstrap';

import DifficultyIcon from '../DifficultyIcon';
import UsersNameContainer from '../../../containers/UsersNameContainer';
Expand All @@ -11,10 +12,11 @@ import DeleteExerciseButtonContainer from '../../../containers/DeleteExerciseBut

import { LocalizedExerciseName } from '../../helpers/LocalizedNames';
import EnvironmentsList from '../../helpers/EnvironmentsList';
import { ExercisePrefixIcons, EditIcon } from '../../icons';
import { ExercisePrefixIcons, EditIcon, LimitsIcon, TestsIcon } from '../../icons';
import Button from '../../widgets/FlatButton';
import DateTime from '../../widgets/DateTime';
import AssignExerciseButton from '../../buttons/AssignExerciseButton';
import { getTagStyle } from '../../../helpers/exercise/tags';

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

Expand All @@ -24,6 +26,7 @@ const ExercisesListItem = ({
difficulty,
authorId,
runtimeEnvironments,
tags,
groupsIds = [],
localizedTexts,
createdAt,
Expand All @@ -46,6 +49,7 @@ const ExercisesListItem = ({
<td className="shrink-col">
<ExercisePrefixIcons id={id} isLocked={isLocked} isBroken={isBroken} />
</td>

<td>
<strong>
{permissionHints.viewDetail ? (
Expand All @@ -57,10 +61,21 @@ const ExercisesListItem = ({
)}
</strong>
</td>

<td>
<UsersNameContainer userId={authorId} />
</td>

<td className="small">{runtimeEnvironments && <EnvironmentsList runtimeEnvironments={runtimeEnvironments} />}</td>

<td className="small">
{tags.sort().map(tag => (
<Label key={tag} className="tag-margin" style={getTagStyle(tag)}>
{tag}
</Label>
))}
</td>

{showGroups && (
<td className="small">
{groupsIds.length > 0 ? (
Expand All @@ -76,13 +91,16 @@ const ExercisesListItem = ({
)}
</td>
)}

<td className="text-nowrap">
<DifficultyIcon difficulty={difficulty} />
</td>

<td className="text-nowrap">
<DateTime
unixts={createdAt}
showOverlay
showTime={false}
overlayTooltipId={`created-tooltip-${id}`}
customTooltip={
<span>
Expand All @@ -102,30 +120,51 @@ const ExercisesListItem = ({
)}
{permissionHints.update && (
<LinkContainer to={EXERCISE_EDIT_URI_FACTORY(id)}>
<Button bsSize="xs" bsStyle="warning">
<EditIcon gapRight />
<FormattedMessage id="app.exercises.listEdit" defaultMessage="Settings" />
</Button>
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id={`${id}-settings`}>
<FormattedMessage id="app.exercises.listEdit" defaultMessage="Settings" />
</Tooltip>
}>
<Button bsSize="xs" bsStyle="warning">
<EditIcon smallGapLeft smallGapRight />
</Button>
</OverlayTrigger>
</LinkContainer>
)}
{permissionHints.viewPipelines && permissionHints.viewScoreConfig && (
<LinkContainer to={EXERCISE_EDIT_CONFIG_URI_FACTORY(id)}>
<Button bsSize="xs" bsStyle={permissionHints.setScoreConfig ? 'warning' : 'default'}>
<EditIcon gapRight />
<FormattedMessage id="app.exercises.listEditConfig" defaultMessage="Tests" />
</Button>
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id={`${id}-tests`}>
<FormattedMessage id="app.exercises.listEditConfig" defaultMessage="Tests" />
</Tooltip>
}>
<Button bsSize="xs" bsStyle={permissionHints.setScoreConfig ? 'warning' : 'default'}>
<TestsIcon smallGapLeft smallGapRight />
</Button>
</OverlayTrigger>
</LinkContainer>
)}
{permissionHints.viewLimits && (
<LinkContainer to={EXERCISE_EDIT_LIMITS_URI_FACTORY(id)}>
<Button bsSize="xs" bsStyle={permissionHints.setLimits ? 'warning' : 'default'}>
<EditIcon gapRight />
<FormattedMessage id="app.exercises.listEditLimits" defaultMessage="Limits" />
</Button>
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id={`${id}-limits`}>
<FormattedMessage id="app.exercises.listEditLimits" defaultMessage="Limits" />
</Tooltip>
}>
<Button bsSize="xs" bsStyle={permissionHints.setLimits ? 'warning' : 'default'}>
<LimitsIcon smallGapLeft smallGapRight />
</Button>
</OverlayTrigger>
</LinkContainer>
)}
{permissionHints.remove && (
<DeleteExerciseButtonContainer id={id} bsSize="xs" resourceless={true} onDeleted={reload} />
<DeleteExerciseButtonContainer id={id} bsSize="xs" resourceless captionAsLabel onDeleted={reload} />
)}
</td>
</tr>
Expand All @@ -135,6 +174,7 @@ ExercisesListItem.propTypes = {
id: PropTypes.string.isRequired,
authorId: PropTypes.string.isRequired,
runtimeEnvironments: PropTypes.array.isRequired,
tags: PropTypes.array.isRequired,
groupsIds: PropTypes.array,
name: PropTypes.string.isRequired,
difficulty: PropTypes.string.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const AssignExerciseButton = ({ isLocked, isBroken, assignExercise, ...props })
);
} else {
return (
<Button onClick={assignExercise} bsSize="xs" className="btn-flat">
<Button onClick={assignExercise} bsSize="xs" className="btn-flat" bsStyle="success">
<SendIcon gapRight />
<FormattedMessage id="app.exercise.assignButton" defaultMessage="Assign" />
</Button>
Expand Down
12 changes: 2 additions & 10 deletions src/components/buttons/DeleteButton/ConfirmDeleteButton.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Button from '../../widgets/FlatButton';
import { DeleteIcon } from '../../icons';
import Confirm from '../../forms/Confirm';
import DeleteButtonRaw from './DeleteButtonRaw';

const ConfirmDeleteButton = ({
id,
onClick,
disabled,
small = true,
question = (
<FormattedMessage
id="app.deleteButton.confirm"
Expand All @@ -19,19 +16,14 @@ const ConfirmDeleteButton = ({
...props
}) => (
<Confirm id={id} onConfirmed={onClick} question={question}>
<Button disabled={disabled || !id} bsStyle="danger" bsSize={small ? 'sm' : undefined} {...props}>
<DeleteIcon gapRight />
<FormattedMessage id="generic.delete" defaultMessage="Delete" />
</Button>
<DeleteButtonRaw id={id} {...props} />
</Confirm>
);

ConfirmDeleteButton.propTypes = {
onClick: PropTypes.func,
id: PropTypes.string,
small: PropTypes.bool,
question: PropTypes.any,
disabled: PropTypes.bool,
};

export default ConfirmDeleteButton;
35 changes: 35 additions & 0 deletions src/components/buttons/DeleteButton/DeleteButtonRaw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
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 ? (
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id={`${id}-delete`}>
<FormattedMessage id="generic.delete" defaultMessage="Delete" />
</Tooltip>
}>
<Button disabled={disabled || !id} bsStyle="danger" bsSize={small ? 'sm' : undefined} {...props}>
<DeleteIcon smallGapLeft smallGapRight />
</Button>
</OverlayTrigger>
) : (
<Button disabled={disabled || !id} bsStyle="danger" bsSize={small ? 'sm' : undefined} {...props}>
<DeleteIcon gapRight />
<FormattedMessage id="generic.delete" defaultMessage="Delete" />
</Button>
);

DeleteButtonRaw.propTypes = {
id: PropTypes.string,
small: PropTypes.bool,
captionAsLabel: PropTypes.bool,
disabled: PropTypes.bool,
};

export default DeleteButtonRaw;
Loading

0 comments on commit 2a39e0e

Please sign in to comment.