Skip to content

Commit

Permalink
Unifying appearance of major action buttons on edit pages.
Browse files Browse the repository at this point in the history
  • Loading branch information
krulis-martin committed Aug 21, 2023
1 parent 0d259d8 commit 7a17b93
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 125 deletions.
14 changes: 3 additions & 11 deletions src/components/buttons/ArchiveGroupButton/ArchiveGroupButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,12 @@ import { FormattedMessage } from 'react-intl';
import { ArchiveGroupIcon, LoadingIcon } from '../../icons';
import Button from '../../widgets/TheButton';

const ArchiveGroupButton = ({
archived,
pending,
disabled = false,
setArchived,
size = undefined,
shortLabels = false,
}) => (
const ArchiveGroupButton = ({ archived, pending, disabled = false, setArchived, shortLabels = false, ...props }) => (
<Button
{...props}
variant={disabled ? 'secondary' : 'info'}
onClick={setArchived(!archived)}
disabled={pending || disabled}
size={size}>
disabled={pending || disabled}>
{pending ? <LoadingIcon gapRight /> : <ArchiveGroupIcon archived={archived} gapRight />}
{archived === true ? (
shortLabels ? (
Expand All @@ -37,7 +30,6 @@ ArchiveGroupButton.propTypes = {
pending: PropTypes.bool.isRequired,
disabled: PropTypes.bool,
setArchived: PropTypes.func.isRequired,
size: PropTypes.string,
shortLabels: PropTypes.bool,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { FormattedMessage } from 'react-intl';
import { GroupIcon, LoadingIcon } from '../../icons';
import Button from '../../widgets/TheButton';

const OrganizationalGroupButton = ({ organizational, pending, disabled = false, setOrganizational }) => (
const OrganizationalGroupButton = ({ organizational, pending, disabled = false, setOrganizational, ...props }) => (
<Button
{...props}
variant={disabled ? 'secondary' : 'info'}
onClick={setOrganizational(!organizational)}
disabled={pending || disabled}>
Expand Down
37 changes: 19 additions & 18 deletions src/components/forms/RelocateGroupForm/RelocateGroupForm.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 { injectIntl, FormattedMessage } from 'react-intl';
import { Form } from 'react-bootstrap';
import { Row, Col } from 'react-bootstrap';
import { reduxForm, Field } from 'redux-form';
import { defaultMemoize } from 'reselect';

Expand Down Expand Up @@ -36,21 +36,22 @@ const RelocateGroupForm = ({
<FormattedMessage id="generic.savingFailed" defaultMessage="Saving failed. Please try again later." />
</Callout>
)}
<Form>
<Field
name={'groupId'}
component={SelectField}
label={<FormattedMessage id="app.relocateGroupForm.parentGroup" defaultMessage="Parent Group:" />}
options={groups
.map(group => ({
key: group.id,
name: getGroupCanonicalLocalizedName(group, groupsAccessor, locale),
}))
.sort((a, b) => a.name.localeCompare(b.name, locale))}
/>

<div className="text-center">
<TheButtonGroup>
<Row className="align-items-end">
<Col xs={12} sm>
<Field
name={'groupId'}
component={SelectField}
label={<FormattedMessage id="app.relocateGroupForm.parentGroup" defaultMessage="Parent Group:" />}
options={groups
.map(group => ({
key: group.id,
name: getGroupCanonicalLocalizedName(group, groupsAccessor, locale),
}))
.sort((a, b) => a.name.localeCompare(b.name, locale))}
/>
</Col>
<Col xs={false} sm="auto">
<TheButtonGroup className="mb-3">
{dirty && (
<Button type="reset" onClick={reset} variant="danger">
<RefreshIcon gapRight />
Expand All @@ -72,8 +73,8 @@ const RelocateGroupForm = ({
}}
/>
</TheButtonGroup>
</div>
</Form>
</Col>
</Row>
</div>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,12 @@ import { identity } from '../../helpers/common';

class ArchiveGroupButtonContainer extends Component {
render() {
const { group, pending, setArchived, size = undefined, ...props } = this.props;
const { group, pending, setArchived, ...props } = this.props;
return (
<ResourceRenderer resource={group}>
{({ directlyArchived, permissionHints }) =>
permissionHints.archive ? (
<ArchiveGroupButton
archived={directlyArchived}
pending={pending}
setArchived={setArchived}
size={size}
{...props}
/>
<ArchiveGroupButton archived={directlyArchived} pending={pending} setArchived={setArchived} {...props} />
) : null
}
</ResourceRenderer>
Expand Down
2 changes: 2 additions & 0 deletions src/locales/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -489,9 +489,11 @@
"app.editExerciseSimpleConfigTests.useOutfile": "Použít výstupní soubor místo std. výstupu",
"app.editExerciseSimpleConfigTests.validation.sentryPointString": "Vstupní bod musí být identifikátor.",
"app.editExerciseTags.noTags": "nejsou přiřazeny žádné nálepky",
"app.editGroup.archiveGroup": "Změnit archivační status",
"app.editGroup.archivedExplain": "Archivované skupiny jsou ohrádky pro studenty, zadané úlohy a jejich řešení u skončených kurzů. Tyto skupiny není možné upravovat a je možné je najít na separátní stránce Archiv.",
"app.editGroup.cannotDeleteGroupWithSubgroups": "Skupinu s podskupinami není možné smazat přímo.",
"app.editGroup.cannotDeleteRootGroup": "Toto je primární skupina a jako taková nemůže být smazána.",
"app.editGroup.changeGroupType": "Změnit typ skupiny",
"app.editGroup.deleteGroup": "Smazat skupinu",
"app.editGroup.deleteGroupWarning": "Smazání skupiny způsobí, že všechny navázané entity (zadané úlohy, odevzdaná řešení, ...) nebudou přístupné.",
"app.editGroup.organizationalExplain": "Běžné skupiny jsou platformou spojující studenty a zadané úlohy. Organizační skupiny slouží pouze k vytváření hierarchie, takže v nich nesmí být přihlášení studenti a nesmí obsahovat zadané úlohy.",
Expand Down
2 changes: 2 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -489,9 +489,11 @@
"app.editExerciseSimpleConfigTests.useOutfile": "Use output file instead of stdout",
"app.editExerciseSimpleConfigTests.validation.sentryPointString": "The entry point value must be an identifier.",
"app.editExerciseTags.noTags": "no tags assigned",
"app.editGroup.archiveGroup": "Change archived status",
"app.editGroup.archivedExplain": "Archived groups are containers for students, assignments and results after the course is finished. They are immutable and can be accessed through separate Archive page.",
"app.editGroup.cannotDeleteGroupWithSubgroups": "Group with nested sub-groups cannot be deleted.",
"app.editGroup.cannotDeleteRootGroup": "This is a so-called root group and it cannot be deleted.",
"app.editGroup.changeGroupType": "Change group type",
"app.editGroup.deleteGroup": "Delete Group",
"app.editGroup.deleteGroupWarning": "Deleting a group will make all attached entities (assignments, solutions, ...) inaccessible.",
"app.editGroup.organizationalExplain": "Regular groups are containers for students and assignments. Organizational groups are intended to create hierarchy, so they are forbidden to hold any students or assignments.",
Expand Down
22 changes: 12 additions & 10 deletions src/pages/EditAssignment/EditAssignment.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,20 +197,22 @@ class EditAssignment extends Component {
title={
<FormattedMessage id="app.editAssignment.deleteAssignment" defaultMessage="Delete the assignment" />
}>
<div>
<p>
<FormattedMessage
id="app.editAssignment.deleteAssignmentWarning"
defaultMessage="Deleting an assignment will remove all the students submissions and you will have to contact the administrator of ReCodEx if you wanted to restore the assignment in the future."
/>
</p>
<p className="text-center">
<Row className="align-items-center">
<Col xs={false} sm="auto">
<DeleteAssignmentButtonContainer
id={assignmentId}
size="lg"
className="m-2"
onDeleted={() => navigate(GROUP_ASSIGNMENTS_URI_FACTORY(assignment.groupId), { replace: true })}
/>
</p>
</div>
</Col>
<Col xs={12} sm className="text-muted">
<FormattedMessage
id="app.editAssignment.deleteAssignmentWarning"
defaultMessage="Deleting an assignment will remove all the students submissions and you will have to contact the administrator of ReCodEx if you wanted to restore the assignment in the future."
/>
</Col>
</Row>
</Box>
)}
</>
Expand Down
121 changes: 64 additions & 57 deletions src/pages/EditGroup/EditGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage, injectIntl } from 'react-intl';
import { Row, Col, Form } from 'react-bootstrap';
import { Row, Col } from 'react-bootstrap';
import { connect } from 'react-redux';
import { reset, formValueSelector } from 'redux-form';
import { defaultMemoize } from 'reselect';
Expand All @@ -18,7 +18,7 @@ import DeleteGroupButtonContainer from '../../containers/DeleteGroupButtonContai
import Box from '../../components/widgets/Box';
import Callout from '../../components/widgets/Callout';
import GroupArchivedWarning from '../../components/Groups/GroupArchivedWarning/GroupArchivedWarning';
import { BanIcon, InfoIcon, EditGroupIcon } from '../../components/icons';
import { BanIcon, EditGroupIcon, WarningIcon } from '../../components/icons';

import { fetchGroup, fetchGroupIfNeeded, editGroup, relocateGroup } from '../../redux/modules/groups';
import {
Expand Down Expand Up @@ -108,47 +108,6 @@ class EditGroup extends Component {

<GroupArchivedWarning {...group} groupsDataAccessor={groupsAccessor} linkFactory={GROUP_EDIT_URI_FACTORY} />

{hasPermissions(group, 'update') && (
<>
{!group.archived && (
<Row>
<Col lg={3}>
<p>
<OrganizationalGroupButtonContainer id={group.id} locale={locale} />
</p>
</Col>
<Col lg={9}>
<p className="small text-muted" style={{ padding: '0.75em' }}>
<InfoIcon gapRight />
<FormattedMessage
id="app.editGroup.organizationalExplain"
defaultMessage="Regular groups are containers for students and assignments. Organizational groups are intended to create hierarchy, so they are forbidden to hold any students or assignments."
/>
</p>
</Col>
</Row>
)}
{hasPermissions(group, 'archive') && (!group.archived || group.directlyArchived) && (
<Row>
<Col lg={3}>
<p>
<ArchiveGroupButtonContainer id={group.id} onChange={reload} />
</p>
</Col>
<Col lg={9}>
<p className="small text-muted" style={{ padding: '0.75em' }}>
<InfoIcon gapRight />
<FormattedMessage
id="app.editGroup.archivedExplain"
defaultMessage="Archived groups are containers for students, assignments and results after the course is finished. They are immutable and can be accessed through separate Archive page."
/>
</p>
</Col>
</Row>
)}
</>
)}

{hasPermissions(group, 'update') && !group.archived && (
<EditGroupForm
form="editGroup"
Expand Down Expand Up @@ -179,20 +138,58 @@ class EditGroup extends Component {
</ResourceRenderer>
)}

{hasPermissions(group, 'update') && (
<>
{!group.archived && (
<Box
type="info"
title={<FormattedMessage id="app.editGroup.changeGroupType" defaultMessage="Change group type" />}>
<Row className="align-items-center">
<Col xs={false} sm="auto">
<OrganizationalGroupButtonContainer id={group.id} size="lg" className="m-2" locale={locale} />
</Col>
<Col xs={12} sm className="text-muted">
<FormattedMessage
id="app.editGroup.organizationalExplain"
defaultMessage="Regular groups are containers for students and assignments. Organizational groups are intended to create hierarchy, so they are forbidden to hold any students or assignments."
/>
</Col>
</Row>
</Box>
)}

{hasPermissions(group, 'archive') && (!group.archived || group.directlyArchived) && (
<Box
type="info"
title={
<FormattedMessage id="app.editGroup.archiveGroup" defaultMessage="Change archived status" />
}>
<Row className="align-items-center">
<Col xs={false} sm="auto">
<ArchiveGroupButtonContainer id={group.id} size="lg" className="m-2" onChange={reload} />
</Col>
<Col xs={12} sm className="text-muted">
<FormattedMessage
id="app.editGroup.archivedExplain"
defaultMessage="Archived groups are containers for students, assignments and results after the course is finished. They are immutable and can be accessed through separate Archive page."
/>
</Col>
</Row>
</Box>
)}
</>
)}

{hasPermissions(group, 'remove') && (
<Box
type="danger"
title={<FormattedMessage id="app.editGroup.deleteGroup" defaultMessage="Delete Group" />}>
<div>
<p>
<FormattedMessage
id="app.editGroup.deleteGroupWarning"
defaultMessage="Deleting a group will make all attached entities (assignments, solutions, ...) inaccessible."
/>
</p>
<p className="text-center">
<Row className="align-items-center">
<Col xs={false} sm="auto">
<DeleteGroupButtonContainer
id={group.id}
size="lg"
className="m-2"
disabled={
group.parentGroupId === null || (group.childGroups && group.childGroups.length > 0) // TODO whatabout archived sub-groups?
}
Expand All @@ -205,26 +202,36 @@ class EditGroup extends Component {
)
}
/>
</Col>
<Col xs={12} sm>
<div className="text-muted">
<FormattedMessage
id="app.editGroup.deleteGroupWarning"
defaultMessage="Deleting a group will make all attached entities (assignments, solutions, ...) inaccessible."
/>
</div>

{group.parentGroupId === null && (
<Form.Text>
<div className="mt-1">
<WarningIcon className="text-danger" gapRight />
<FormattedMessage
id="app.editGroup.cannotDeleteRootGroup"
defaultMessage="This is a so-called root group and it cannot be deleted."
/>
</Form.Text>
</div>
)}

{group.parentGroupId !== null && group.childGroups && group.childGroups.length > 0 && (
<Form.Text>
<div className="mt-1">
<WarningIcon className="text-danger" gapRight />
<FormattedMessage
id="app.editGroup.cannotDeleteGroupWithSubgroups"
defaultMessage="Group with nested sub-groups cannot be deleted."
/>
</Form.Text>
</div>
)}
</p>
</div>
</Col>
</Row>
</Box>
)}
</div>
Expand Down
22 changes: 12 additions & 10 deletions src/pages/EditPipeline/EditPipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,20 +167,22 @@ class EditPipeline extends Component {
<Box
type="danger"
title={<FormattedMessage id="app.editPipeline.delete" defaultMessage="Delete the pipeline" />}>
<div>
<p>
<FormattedMessage
id="app.editPipeline.deleteWarning"
defaultMessage="Deleting an pipeline will break all exercises using the pipeline."
/>
</p>
<p className="text-center">
<Row className="align-items-center">
<Col xs={false} sm="auto">
<DeletePipelineButtonContainer
id={pipeline.id}
size="lg"
className="m-2"
onDeleted={() => navigate(PIPELINES_URI, { replace: true })}
/>
</p>
</div>
</Col>
<Col xs={12} sm className="text-muted">
<FormattedMessage
id="app.editPipeline.deleteWarning"
defaultMessage="Deleting an pipeline will break all exercises using the pipeline."
/>
</Col>
</Row>
</Box>
</Col>
</Row>
Expand Down
Loading

0 comments on commit 7a17b93

Please sign in to comment.