Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add remove instructions button modal #1176

Merged
merged 28 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
423fe55
new prop for editable instructions (#1161)
conorriches Jan 14, 2025
ffc100b
Show instructions if they are editable (#1164)
loiswells97 Jan 15, 2025
28edde7
Autosave instructions (#1163)
conorriches Jan 15, 2025
98a9c90
Merge branch 'main' into feat/editable-instructions
loiswells97 Jan 16, 2025
59c798c
Instructions empty state (#1165)
loiswells97 Jan 16, 2025
6bacafd
Fix markdown link (#1168)
loiswells97 Jan 17, 2025
430dae1
Merge branch 'main' into feat/editable-instructions
loiswells97 Jan 17, 2025
c579212
Create RemoveInstructionsModal
jamiebenstead Jan 22, 2025
5a2cdf2
Fix cached instructions (#1169)
loiswells97 Jan 23, 2025
04b82a3
Add remove instructions modal to instructions panel
jamiebenstead Jan 23, 2025
99e9e20
Merge branch 'feat/editable-instructions' of https://github.com/Raspb…
jamiebenstead Jan 23, 2025
5f2916b
Add missing question mark
jamiebenstead Jan 23, 2025
7a4ce9a
Fix logic to switch between add and remove instructions sections
jamiebenstead Jan 23, 2025
1e9f50e
Add missing div to content
jamiebenstead Jan 24, 2025
19f77f3
Add keys to buttons
jamiebenstead Jan 24, 2025
7b3d36c
Add tests for remove instructions button
jamiebenstead Jan 24, 2025
4e3a057
Update test to allow for empty array
jamiebenstead Jan 24, 2025
2070659
Fix linting issues
jamiebenstead Jan 28, 2025
793b7c1
Instructions tabs (#1167)
conorriches Jan 28, 2025
f08dc7d
Merge branch 'feat/editable-instructions' into add-remove-instruction…
jamiebenstead Jan 29, 2025
8b40d73
Update changelog
jamiebenstead Jan 29, 2025
c65d70a
Merge branch 'main' of https://github.com/RaspberryPiFoundation/edito…
jamiebenstead Jan 29, 2025
840b4af
Merge branch 'feat/editable-instructions' of https://github.com/Raspb…
jamiebenstead Jan 29, 2025
e471157
Change logic for setOption
jamiebenstead Jan 30, 2025
4d0df35
Merge branch 'main' into add-remove-instructions-button-modal
jamiebenstead Jan 30, 2025
ddc1c29
Linter fixes
jamiebenstead Jan 30, 2025
8081c89
Move translation to more sensible place
jamiebenstead Jan 30, 2025
8cc5986
Fix linting issue
jamiebenstead Jan 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Instructions empty state to show when instructions are editable (#1165, #1168)
- Allow `instructions` attribute to override instructions attached to the project (#1169)
- Instructions tabs for edit and viewing (#1167)

- Add remove instructions button modal (#1176)

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import { setCurrentStepPosition } from "../../../../redux/InstructionsSlice";
import DesignSystemButton from "../../../DesignSystemButton/DesignSystemButton";
import { setProjectInstructions } from "../../../../redux/EditorSlice";
import demoInstructions from "../../../../assets/markdown/demoInstructions.md";
import RemoveInstructionsModal from "../../../Modals/RemoveInstructionsModal";

const InstructionsPanel = () => {
const [showModal, setShowModal] = useState(false);
const instructionsEditable = useSelector(
(state) => state.editor?.instructionsEditable,
);
Expand Down Expand Up @@ -104,6 +106,11 @@ const InstructionsPanel = () => {
dispatch(setProjectInstructions(demoInstructions));
};

const removeInstructions = () => {
dispatch(setProjectInstructions(null));
setShowModal(false);
};

const AddInstructionsButton = () => {
return (
<DesignSystemButton
Expand All @@ -118,6 +125,20 @@ const InstructionsPanel = () => {
);
};

const RemoveInstructionsButton = () => {
return (
<DesignSystemButton
className="btn--secondary"
text={t("instructionsPanel.removeInstructions")}
onClick={() => {
setShowModal(true);
}}
fill
textAlways
small
/>
);
};
const onChange = (e) => {
dispatch(setProjectInstructions(e.target.value));
};
Expand All @@ -126,7 +147,13 @@ const InstructionsPanel = () => {
<SidebarPanel
defaultWidth="30vw"
heading={t("instructionsPanel.projectSteps")}
Button={instructionsEditable && !hasInstructions && AddInstructionsButton}
Button={
instructionsEditable
? hasInstructions
? RemoveInstructionsButton
: AddInstructionsButton
: null
}
{...{ Footer: hasMultipleSteps && ProgressBar }}
>
<div className="project-instructions">
Expand Down Expand Up @@ -191,6 +218,29 @@ const InstructionsPanel = () => {
></div>
)}
</div>
{showModal && (
<RemoveInstructionsModal
buttons={[
<DesignSystemButton
type="primary"
key="remove"
variant="danger"
text={t(
"instructionsPanel.removeInstructionsModal.removeInstructions",
)}
onClick={removeInstructions}
/>,
<DesignSystemButton
type="secondary"
key="close"
text={t("instructionsPanel.removeInstructionsModal.close")}
onClick={() => setShowModal(false)}
/>,
]}
isOpen={showModal}
setShowModal={setShowModal}
/>
)}
</SidebarPanel>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Provider } from "react-redux";
import configureStore from "redux-mock-store";
import { setProjectInstructions } from "../../../../redux/EditorSlice";
import { act } from "react";
import Modal from "react-modal";

window.HTMLElement.prototype.scrollTo = jest.fn();
window.Prism = {
Expand All @@ -14,6 +15,13 @@ describe("When instructionsEditable is true", () => {
describe("When there are instructions", () => {
let store;

beforeAll(() => {
const root = global.document.createElement("div");
root.setAttribute("id", "app");
global.document.body.appendChild(root);
Modal.setAppElement("#app");
});

beforeEach(() => {
const mockStore = configureStore([]);
const initialState = {
Expand Down Expand Up @@ -67,6 +75,21 @@ describe("When instructionsEditable is true", () => {
screen.queryByText("instructionsPanel.emptyState.addInstructions"),
).not.toBeInTheDocument();
});

test("Renders the remove instructions button", () => {
expect(
screen.queryByText("instructionsPanel.removeInstructions"),
).toBeInTheDocument();
});

test("Remove instructions modal is opened", () => {
const button = screen.queryByText("instructionsPanel.removeInstructions");
fireEvent.click(button);

expect(
screen.queryByText("instructionsPanel.removeInstructionsModal.heading"),
).toBeInTheDocument();
});
});

describe("When there are no instructions", () => {
Expand Down Expand Up @@ -101,6 +124,12 @@ describe("When instructionsEditable is true", () => {
).toBeInTheDocument();
});

test("Does not render the remove instructions button", () => {
expect(
screen.queryByText("instructionsPanel.removeInstructions"),
).not.toBeInTheDocument();
});

test("Clicking the add instructions button adds the demo instructions", () => {
const addInstructionsButton = screen.getByText(
"instructionsPanel.emptyState.addInstructions",
Expand Down Expand Up @@ -153,6 +182,12 @@ describe("When instructions are not editable", () => {
).not.toBeInTheDocument();
});

test("Does not render the remove instructions button", () => {
expect(
screen.queryByText("instructionsPanel.removeInstructions"),
).not.toBeInTheDocument();
});

test("Does not render the instructions explanation", () => {
expect(
screen.queryByText("instructionsPanel.emptyState.purpose"),
Expand Down
6 changes: 4 additions & 2 deletions src/components/Menus/Sidebar/Sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,13 @@ const Sidebar = ({ options = [] }) => {
instructionsEditable || instructionsSteps ? "instructions" : "file",
);

const hasInstructions = instructionsSteps && instructionsSteps.length > 0;

useEffect(() => {
if (instructionsEditable || instructionsSteps) {
if (instructionsEditable || hasInstructions) {
setOption("instructions");
}
}, [instructionsEditable, instructionsSteps]);
}, [instructionsEditable, hasInstructions]);

const toggleOption = (newOption) => {
if (option !== newOption) {
Expand Down
60 changes: 60 additions & 0 deletions src/components/Modals/RemoveInstructionsModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from "react";
import { useTranslation } from "react-i18next";

import GeneralModal from "./GeneralModal";

const RemoveInstructionsModal = (props) => {
const {
buttons = null,
isOpen = false,
withCloseButton = false,
setShowModal = null,
} = props;

const { t } = useTranslation();

return (
<GeneralModal
heading={t("instructionsPanel.removeInstructionsModal.heading")}
text={[
{
type: "paragraph",
content: t(
"instructionsPanel.removeInstructionsModal.removeInstuctionsWarning",
),
},
]}
buttons={buttons}
isOpen={isOpen}
closeModal={() => setShowModal(false)}
withCloseButton={withCloseButton}
>
<div>
<p>
{t(
"instructionsPanel.removeInstructionsModal.resultRemovingInstructions",
)}
</p>
<ul>
<li>
{t(
"instructionsPanel.removeInstructionsModal.instructionsWillBeDeleted",
)}
</li>
<li>
{t(
"instructionsPanel.removeInstructionsModal.studentsWorkingProjectNotRecievedInstructions",
)}
</li>
<li>
{t(
"instructionsPanel.removeInstructionsModal.studentsStartedWillSeeInstructions",
)}
</li>
</ul>
</div>
</GeneralModal>
);
};

export default RemoveInstructionsModal;
18 changes: 10 additions & 8 deletions src/components/WebComponentProject/WebComponentProject.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,20 @@ const WebComponentProject = ({
}, [projectIdentifier]);

useEffect(() => {
if (!projectInstructions || !permitInstructionsOverride) return;
if (!permitInstructionsOverride) return;

dispatch(
setInstructions({
project: {
steps: [
{
quiz: false,
title: "",
content: marked.parse(projectInstructions),
},
],
steps: projectInstructions
? [
{
quiz: false,
title: "",
content: marked.parse(projectInstructions),
},
]
: [],
},
permitOverride: true,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,16 @@ describe("When there are no instructions", () => {
});

test("Does not dispatch action to set instructions", () => {
expect(store.getActions()).not.toEqual(
expect(store.getActions()).toEqual(
expect.arrayContaining([
{
type: "instructions/setInstructions",
payload: expect.any(Object),
payload: {
permitOverride: true,
project: {
steps: [],
},
},
},
]),
);
Expand Down
16 changes: 16 additions & 0 deletions src/utils/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,27 @@ i18n
purpose:
"Instructions can be added to your project to guide students.",
},
removeInstructions: "Remove instructions",
nextStep: "Next step",
previousStep: "Previous step",
projectSteps: "Project steps",
edit: "Edit",
view: "View",
removeInstructionsModal: {
removeInstructions: "Remove instructions",
close: "Close",
heading: "Remove project instructions?",
removeInstuctionsWarning:
"You are about to remove the instructions from the project.",
resultRemovingInstructions:
"As a result of removing these instructions:",
instructionsWillBeDeleted:
"Instructions content will be deleted.",
studentsWorkingProjectNotRecievedInstructions:
"Students who start working on this project will not receive instructions.",
studentsStartedWillSeeInstructions:
"Students who have already started working on this project will still be able to see the instructions as they were when they started.",
},
},
projectsPanel: {
projects: "Projects",
Expand Down
Loading