From 423fe5538d60c5db722db589cdd9d3f4fa2ed40d Mon Sep 17 00:00:00 2001 From: Conor <905676+conorriches@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:36:49 +0000 Subject: [PATCH 01/21] new prop for editable instructions (#1161) * unblocks us * merging into feature branch --- CHANGELOG.md | 1 + README.md | 1 + .../InstructionsPanel/InstructionsPanel.jsx | 4 ++ .../InstructionsPanel.test.js | 41 +++++++++++++++++++ .../ProgressBar/ProgressBar.jsx | 5 +++ .../ProgressBar/ProgressBar.test.js | 30 +++++++++++--- .../WebComponentProject.jsx | 27 +++++++++++- src/containers/WebComponentLoader.jsx | 2 + src/redux/EditorSlice.js | 4 ++ src/web-component.js | 2 + 10 files changed, 110 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e087a85cb..90a15dd8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added +- Editable instructions - Support for the `outputPanels` attribute in the `PyodideRunner` (#1157) ## [0.28.14] - 2025-01-06 diff --git a/README.md b/README.md index 54281f396..652313d25 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ The `editor-wc` tag accepts the following attributes, which must be provided as - `assets_identifier`: Load assets (not code) from this project identifier - `auth_key`: Authenticate the user to allow them to make API requests such as saving their work - `code`: A preset blob of code to show in the editor pane (overrides content of `main.py`/`index.html`) +- `editable_instructions`: Boolean whether to show edit panel for instructions - `embedded`: Enable embedded mode which hides some functionality (defaults to `false`) - `host_styles`: Styles passed into the web component from the host page - `identifier`: Load the project with this identifier from the database diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx index 03b711a99..4018ee125 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx @@ -12,6 +12,9 @@ import { quizReadyEvent } from "../../../../events/WebComponentCustomEvents"; import { setCurrentStepPosition } from "../../../../redux/InstructionsSlice"; const InstructionsPanel = () => { + const instructionsEditable = useSelector( + (state) => state.editor?.instructionsEditable, + ); const steps = useSelector((state) => state.instructions.project?.steps); const quiz = useSelector((state) => state.instructions?.quiz); const dispatch = useDispatch(); @@ -82,6 +85,7 @@ const InstructionsPanel = () => { heading={t("instructionsPanel.projectSteps")} Footer={ProgressBar} > +
{instructionsEditable &&

Edit panel will go here

}
); diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js index 890cc3e2c..e3f6cdbe7 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js @@ -12,6 +12,9 @@ describe("It renders project steps when there is no quiz", () => { beforeEach(() => { const mockStore = configureStore([]); const initialState = { + editor: { + instructionsEditable: false, + }, instructions: { project: { steps: [ @@ -67,6 +70,44 @@ describe("It renders project steps when there is no quiz", () => { }); }); +describe("When instructionsEditable is true", () => { + beforeEach(() => { + const mockStore = configureStore([]); + const initialState = { + editor: { + instructionsEditable: true, + }, + instructions: { + project: { + steps: [ + { content: "

step 0

" }, + { + content: `

step 1

+ print('hello') +

Hello world

+ .hello { color: purple } + `, + }, + ], + }, + quiz: {}, + currentStepPosition: 1, + }, + }; + const store = mockStore(initialState); + render( + + + , + ); + }); + + test("Renders the edit panel", () => { + // TODO: CR: 2024-01-14: Add edit panel + expect(screen.queryByText("Edit panel will go here")).toBeInTheDocument(); + }); +}); + describe("It renders a quiz when it has one", () => { const quizHandler = jest.fn(); diff --git a/src/components/Menus/Sidebar/InstructionsPanel/ProgressBar/ProgressBar.jsx b/src/components/Menus/Sidebar/InstructionsPanel/ProgressBar/ProgressBar.jsx index 01487a7da..b04347aa3 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/ProgressBar/ProgressBar.jsx +++ b/src/components/Menus/Sidebar/InstructionsPanel/ProgressBar/ProgressBar.jsx @@ -30,6 +30,11 @@ const ProgressBar = () => { const goToPreviousStep = () => { dispatch(setCurrentStepPosition(Math.max(currentStepPosition - 1, 0))); }; + + if (numberOfSteps === 1) { + return <>; + } + return (
); }; From 19f77f39b5f60bc3838f15574781ad65aa8c7083 Mon Sep 17 00:00:00 2001 From: Jamie Benstead Date: Fri, 24 Jan 2025 15:33:40 +0000 Subject: [PATCH 12/21] Add keys to buttons --- .../Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx index d3676cc3e..94bbf966a 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx @@ -202,6 +202,7 @@ const InstructionsPanel = () => { buttons={[ { />, setShowModal(false)} />, From 7b3d36c3adc0d4b52ae21e40ccc81f9039b61835 Mon Sep 17 00:00:00 2001 From: Jamie Benstead Date: Fri, 24 Jan 2025 15:34:04 +0000 Subject: [PATCH 13/21] Add tests for remove instructions button --- .../InstructionsPanel.test.js | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js index d21a4ca05..8e48d73ae 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js @@ -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 = { @@ -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 = { @@ -59,6 +67,23 @@ describe("When instructionsEditable is true", () => { screen.queryByText("instructionsPanel.emptyState.addInstructions"), ).not.toBeInTheDocument(); }); + + test("Renders the remove instructions button", () => { + expect( + screen.queryByText("instructionsPanel.emptyState.removeInstructions"), + ).toBeInTheDocument(); + }); + + test("Remove instructions modal is opened", () => { + const button = screen.queryByText( + "instructionsPanel.emptyState.removeInstructions", + ); + fireEvent.click(button); + + expect( + screen.queryByText("instructionsPanel.removeInstructionsModal.heading"), + ).toBeInTheDocument(); + }); }); describe("When there are no instructions", () => { @@ -87,12 +112,18 @@ describe("When instructionsEditable is true", () => { ); }); - test("Renders the add instrucitons button", () => { + test("Renders the add instructions button", () => { expect( screen.queryByText("instructionsPanel.emptyState.addInstructions"), ).toBeInTheDocument(); }); + test("Does not render the remove instructions button", () => { + expect( + screen.queryByText("instructionsPanel.emptyState.removeInstructions"), + ).not.toBeInTheDocument(); + }); + test("Clicking the add instructions button adds the demo instructions", () => { const addInstructionsButton = screen.getByText( "instructionsPanel.emptyState.addInstructions", @@ -145,6 +176,12 @@ describe("When instructions are not editable", () => { ).not.toBeInTheDocument(); }); + test("Does not render the remove instructions button", () => { + expect( + screen.queryByText("instructionsPanel.emptyState.removeInstructions"), + ).not.toBeInTheDocument(); + }); + test("Does not render the instructions explanation", () => { expect( screen.queryByText("instructionsPanel.emptyState.purpose"), From 4e3a0571b4b4d17553271e122b35f718c3ea779e Mon Sep 17 00:00:00 2001 From: Jamie Benstead Date: Fri, 24 Jan 2025 15:34:31 +0000 Subject: [PATCH 14/21] Update test to allow for empty array --- .../WebComponentProject/WebComponentProject.test.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/WebComponentProject/WebComponentProject.test.js b/src/components/WebComponentProject/WebComponentProject.test.js index 2141c095d..7aaad7e45 100644 --- a/src/components/WebComponentProject/WebComponentProject.test.js +++ b/src/components/WebComponentProject/WebComponentProject.test.js @@ -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: [], + }, + }, }, ]), ); From 20706594442770b93f9922de95a6623ecd93c4f2 Mon Sep 17 00:00:00 2001 From: Jamie Benstead Date: Tue, 28 Jan 2025 10:27:08 +0000 Subject: [PATCH 15/21] Fix linting issues --- .../InstructionsPanel/InstructionsPanel.jsx | 14 +++++++------- src/components/Modals/RemoveInstructionsModal.jsx | 10 +++++----- .../WebComponentProject/WebComponentProject.jsx | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx index 94bbf966a..8d129dc2e 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx @@ -19,14 +19,14 @@ import RemoveInstructionsModal from "../../../Modals/RemoveInstructionsModal"; const InstructionsPanel = () => { const [showModal, setShowModal] = useState(false); const instructionsEditable = useSelector( - (state) => state.editor?.instructionsEditable + (state) => state.editor?.instructionsEditable, ); const project = useSelector((state) => state.editor?.project); const steps = useSelector((state) => state.instructions.project?.steps); const quiz = useSelector((state) => state.instructions?.quiz); const dispatch = useDispatch(); const currentStepPosition = useSelector( - (state) => state.instructions.currentStepPosition + (state) => state.instructions.currentStepPosition, ); const { t } = useTranslation(); const stepContent = useRef(); @@ -38,7 +38,7 @@ const InstructionsPanel = () => { }, [quiz]); const numberOfSteps = useSelector( - (state) => state.instructions.project?.steps?.length || 0 + (state) => state.instructions.project?.steps?.length || 0, ); const hasInstructions = steps && steps.length > 0; @@ -46,7 +46,7 @@ const InstructionsPanel = () => { const applySyntaxHighlighting = (container) => { const codeElements = container.querySelectorAll( - ".language-python, .language-html, .language-css" + ".language-python, .language-html, .language-css", ); codeElements.forEach((element) => { @@ -92,8 +92,8 @@ const InstructionsPanel = () => { if (quizCompleted && isQuiz) { dispatch( setCurrentStepPosition( - Math.min(currentStepPosition + 1, numberOfSteps - 1) - ) + Math.min(currentStepPosition + 1, numberOfSteps - 1), + ), ); } }, [quizCompleted, currentStepPosition, numberOfSteps, dispatch, isQuiz]); @@ -205,7 +205,7 @@ const InstructionsPanel = () => { key="remove" variant="danger" text={t( - "instructionsPanel.removeInstructionsModal.removeInstructions" + "instructionsPanel.removeInstructionsModal.removeInstructions", )} onClick={removeInstructions} />, diff --git a/src/components/Modals/RemoveInstructionsModal.jsx b/src/components/Modals/RemoveInstructionsModal.jsx index 9ed4f65ec..cb1422d6b 100644 --- a/src/components/Modals/RemoveInstructionsModal.jsx +++ b/src/components/Modals/RemoveInstructionsModal.jsx @@ -20,7 +20,7 @@ const RemoveInstructionsModal = (props) => { { type: "paragraph", content: t( - "instructionsPanel.removeInstructionsModal.removeInstuctionsWarning" + "instructionsPanel.removeInstructionsModal.removeInstuctionsWarning", ), }, ]} @@ -32,23 +32,23 @@ const RemoveInstructionsModal = (props) => {

{t( - "instructionsPanel.removeInstructionsModal.resultRemovingInstructions" + "instructionsPanel.removeInstructionsModal.resultRemovingInstructions", )}

  • {t( - "instructionsPanel.removeInstructionsModal.instructionsWillBeDeleted" + "instructionsPanel.removeInstructionsModal.instructionsWillBeDeleted", )}
  • {t( - "instructionsPanel.removeInstructionsModal.studentsWorkingProjectNotRecievedInstructions" + "instructionsPanel.removeInstructionsModal.studentsWorkingProjectNotRecievedInstructions", )}
  • {t( - "instructionsPanel.removeInstructionsModal.studentsStartedWillSeeInstructions" + "instructionsPanel.removeInstructionsModal.studentsStartedWillSeeInstructions", )}
diff --git a/src/components/WebComponentProject/WebComponentProject.jsx b/src/components/WebComponentProject/WebComponentProject.jsx index 8e5d6ab5e..1a17274cb 100644 --- a/src/components/WebComponentProject/WebComponentProject.jsx +++ b/src/components/WebComponentProject/WebComponentProject.jsx @@ -39,23 +39,23 @@ const WebComponentProject = ({ const loading = useSelector((state) => state.editor.loading); const project = useSelector((state) => state.editor.project); const projectIdentifier = useSelector( - (state) => state.editor.project.identifier + (state) => state.editor.project.identifier, ); const codeRunTriggered = useSelector( - (state) => state.editor.codeRunTriggered + (state) => state.editor.codeRunTriggered, ); const error = useSelector((state) => state.editor.error); const errorDetails = useSelector((state) => state.editor.errorDetails); const codeHasBeenRun = useSelector((state) => state.editor.codeHasBeenRun); const projectInstructions = useSelector( - (state) => state.editor.project.instructions + (state) => state.editor.project.instructions, ); const currentStepPosition = useSelector( - (state) => state.instructions.currentStepPosition + (state) => state.instructions.currentStepPosition, ); const permitInstructionsOverride = useSelector( - (state) => state.instructions.permitOverride + (state) => state.instructions.permitOverride, ); const isMobile = useMediaQuery({ query: MOBILE_MEDIA_QUERY }); const [codeHasRun, setCodeHasRun] = useState(codeHasBeenRun); @@ -99,7 +99,7 @@ const WebComponentProject = ({ : [], }, permitOverride: true, - }) + }), ); }, [dispatch, projectInstructions, permitInstructionsOverride]); From 793b7c102f219eb81e2ceaca4520383697f07953 Mon Sep 17 00:00:00 2001 From: Conor <905676+conorriches@users.noreply.github.com> Date: Tue, 28 Jan 2025 16:44:08 +0000 Subject: [PATCH 16/21] Instructions tabs (#1167) ![image](https://github.com/user-attachments/assets/5113c884-0960-4694-923f-b1d98c655379) --------- Co-authored-by: Lois Wells <88904316+loiswells97@users.noreply.github.com> --- CHANGELOG.md | 9 +-- src/assets/stylesheets/Instructions.scss | 65 ++++++++++++++++--- .../InstructionsPanel/InstructionsPanel.jsx | 40 +++++++++--- .../InstructionsPanel.test.js | 16 +++++ src/utils/i18n.js | 2 + 5 files changed, 110 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d670c2a63..da2fb8d08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,15 +8,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added -- Autosave instructions -- Editable instructions +- Autosave instructions (#1163) +- Editable instructions (#1161) - Ability to write to files in `python` (#1146) - Support for the `outputPanels` attribute in the `PyodideRunner` (#1157) - Downloading project instructions (#1160) - Show instructions option in sidebar if instructions are editable (#1164) - Open instructions panel by default if instructions are editable (#1164) -- Instructions empty state to show when instructions are editable (#1165, ##1168) -- Allow `instructions` attribute to override instructions attached to the project (#1169) +- 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) ### Changed diff --git a/src/assets/stylesheets/Instructions.scss b/src/assets/stylesheets/Instructions.scss index 109aa9e65..c3bab4c8a 100644 --- a/src/assets/stylesheets/Instructions.scss +++ b/src/assets/stylesheets/Instructions.scss @@ -1,10 +1,14 @@ -@use "../../../node_modules/@raspberrypifoundation/design-system-core/scss/components/squiggle.scss" as *; +@use "../../../node_modules/@raspberrypifoundation/design-system-core/scss/components/squiggle.scss" + as *; +@use "../../../node_modules/@raspberrypifoundation/design-system-core/scss/mixins/typography"; @use "./rpf_design_system/colours" as *; @use "./rpf_design_system/spacing" as *; -@use './rpf_design_system/font-size' as *; +@use "./rpf_design_system/font-size" as *; @use "./rpf_design_system/font-weight" as *; .project-instructions { + block-size: 100%; + h2 { @include font-size-1-25(bold); margin: 0; @@ -72,14 +76,11 @@ border-block-end: 1px solid $rpf-grey-600; } - - .line-numbers { padding-inline-start: $space-3; padding-inline-end: $space-1; } - .line-numbers-rows { border-color: $rpf-text-secondary-dark; @@ -97,13 +98,16 @@ } .language-python { - .number, .boolean, .function { + .number, + .boolean, + .function { color: $rpf-syntax-1; } .keyword { color: $rpf-syntax-4; } - .string, .char { + .string, + .char { color: $rpf-syntax-2; } .comment { @@ -129,7 +133,8 @@ color: $rpf-syntax-4; } - .property, .punctuation { + .property, + .punctuation { color: $rpf-white; } } @@ -137,7 +142,8 @@ .language-html { .tag { color: $rpf-syntax-4; - .punctuation, .attr-name { + .punctuation, + .attr-name { color: $rpf-white; } @@ -269,3 +275,44 @@ margin: 0; } } + +#app, +#wc { + .c-instruction-tabs { + display: flex; + flex-direction: column; + block-size: 100%; + + .react-tabs { + border: 1px solid var(--editor-color-outline); + + .react-tabs__tab-list { + border-block-end: 1px solid var(--editor-color-outline); + } + } + + .react-tabs__tab { + background: var(--rpf-off-white); + padding-inline: var(--space-1-5); + + &--selected { + background: var(--rpf-white); + } + } + + .react-tabs__tab-panel { + .project-instructions { + padding-inline: var(--space-1); + } + } + + textarea { + @include typography.style-1(); + + border: none; + block-size: 100%; + overflow-block: scroll; + padding: var(--space-1); + } + } +} diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx index b3574e308..d57e6cf98 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx @@ -2,6 +2,9 @@ import React, { useEffect, useRef, useMemo, useState } from "react"; import SidebarPanel from "../SidebarPanel"; import { Trans, useTranslation } from "react-i18next"; import { useSelector, useDispatch } from "react-redux"; +import { Tabs, TabList, Tab, TabPanel } from "react-tabs"; +import { Link } from "react-router-dom"; + import ProgressBar from "./ProgressBar/ProgressBar"; import "../../../../assets/stylesheets/Instructions.scss"; import "prismjs/plugins/highlight-keywords/prism-highlight-keywords.js"; @@ -13,7 +16,6 @@ import { setCurrentStepPosition } from "../../../../redux/InstructionsSlice"; import DesignSystemButton from "../../../DesignSystemButton/DesignSystemButton"; import { setProjectInstructions } from "../../../../redux/EditorSlice"; import demoInstructions from "../../../../assets/markdown/demoInstructions.md"; -import { Link } from "react-router-dom"; const InstructionsPanel = () => { const instructionsEditable = useSelector( @@ -30,6 +32,7 @@ const InstructionsPanel = () => { const stepContent = useRef(); const [isQuiz, setIsQuiz] = useState(false); + const [instructionsTab, setInstructionsTab] = useState(0); const quizCompleted = useMemo(() => { return quiz?.currentQuestion === quiz?.questionCount; @@ -84,6 +87,7 @@ const InstructionsPanel = () => { quiz, quizCompleted, isQuiz, + instructionsTab, ]); useEffect(() => { @@ -128,14 +132,32 @@ const InstructionsPanel = () => {
{instructionsEditable ? ( hasInstructions ? ( -
- {instructionsEditable && ( - - )} +
+ { + setInstructionsTab(index); + }} + > + + {t("instructionsPanel.edit")} + {t("instructionsPanel.view")} + + + + + + <> +
+ +
+
) : (
diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js index d21a4ca05..54c443aae 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js @@ -37,6 +37,14 @@ describe("When instructionsEditable is true", () => { ); }); + test("Renders two tab titles", () => { + expect(screen.getAllByRole("tab")).toHaveLength(2); + }); + + test("Renders two tab panels", () => { + expect(screen.getAllByRole("tabpanel")).toHaveLength(2); + }); + test("Renders the edit panel", () => { expect(screen.getByTestId("instructionTextarea")).toBeInTheDocument(); }); @@ -195,6 +203,14 @@ describe("When instructions are not editable", () => { ); }); + test("Renders no tab titles", () => { + expect(screen.queryAllByRole("tab")).toHaveLength(0); + }); + + test("Renders no tab panels", () => { + expect(screen.queryAllByRole("tabpanel")).toHaveLength(0); + }); + test("Renders with correct instruction step content", () => { expect(screen.queryByText("step 1")).toBeInTheDocument(); }); diff --git a/src/utils/i18n.js b/src/utils/i18n.js index 8b538c8b4..c69e5a40c 100644 --- a/src/utils/i18n.js +++ b/src/utils/i18n.js @@ -219,6 +219,8 @@ i18n nextStep: "Next step", previousStep: "Previous step", projectSteps: "Project steps", + edit: "Edit", + view: "View", }, projectsPanel: { projects: "Projects", From 8b40d7349bb04cc4610aff7cff0aaebbbede84db Mon Sep 17 00:00:00 2001 From: Jamie Benstead Date: Wed, 29 Jan 2025 14:21:18 +0000 Subject: [PATCH 17/21] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da2fb8d08..bb6d2a376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,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 From e471157abc3bf05d070209c74a0ca7f82eefd31a Mon Sep 17 00:00:00 2001 From: Jamie Benstead Date: Thu, 30 Jan 2025 12:24:47 +0000 Subject: [PATCH 18/21] Change logic for setOption --- src/components/Menus/Sidebar/Sidebar.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/Menus/Sidebar/Sidebar.jsx b/src/components/Menus/Sidebar/Sidebar.jsx index 18205614f..86987a52b 100644 --- a/src/components/Menus/Sidebar/Sidebar.jsx +++ b/src/components/Menus/Sidebar/Sidebar.jsx @@ -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) { From ddc1c29a227898b1e99c6d003be14bb260952a32 Mon Sep 17 00:00:00 2001 From: Jamie Benstead Date: Thu, 30 Jan 2025 12:46:38 +0000 Subject: [PATCH 19/21] Linter fixes --- .../Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx | 2 +- .../Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx index 97e74e428..41f60aa72 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx @@ -110,7 +110,7 @@ const InstructionsPanel = () => { dispatch(setProjectInstructions(null)); setShowModal(false); }; - + const AddInstructionsButton = () => { return ( { ); }); - test("Renders the add instrucitons button", () => { expect( screen.queryByText("instructionsPanel.emptyState.addInstructions"), @@ -257,7 +256,6 @@ describe("When instructions are not editable", () => { expect(window.HTMLElement.prototype.scrollTo).toHaveBeenCalledWith({ top: 0, }); - }); test("Renders the progress bar", () => { From 8081c89061dd30f43ce9d3db0682d3847dbd7269 Mon Sep 17 00:00:00 2001 From: Jamie Benstead Date: Thu, 30 Jan 2025 15:35:13 +0000 Subject: [PATCH 20/21] Move translation to more sensible place --- .../Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx | 2 +- .../Sidebar/InstructionsPanel/InstructionsPanel.test.js | 8 ++++---- src/utils/i18n.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx index 41f60aa72..2371ec5a9 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx @@ -129,7 +129,7 @@ const InstructionsPanel = () => { return ( { setShowModal(true); }} diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js index 2c6bcce10..123544f62 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js @@ -78,13 +78,13 @@ describe("When instructionsEditable is true", () => { test("Renders the remove instructions button", () => { expect( - screen.queryByText("instructionsPanel.emptyState.removeInstructions"), + screen.queryByText("instructionsPanel.removeInstructions"), ).toBeInTheDocument(); }); test("Remove instructions modal is opened", () => { const button = screen.queryByText( - "instructionsPanel.emptyState.removeInstructions", + "instructionsPanel.removeInstructions", ); fireEvent.click(button); @@ -128,7 +128,7 @@ describe("When instructionsEditable is true", () => { test("Does not render the remove instructions button", () => { expect( - screen.queryByText("instructionsPanel.emptyState.removeInstructions"), + screen.queryByText("instructionsPanel.removeInstructions"), ).not.toBeInTheDocument(); }); @@ -186,7 +186,7 @@ describe("When instructions are not editable", () => { test("Does not render the remove instructions button", () => { expect( - screen.queryByText("instructionsPanel.emptyState.removeInstructions"), + screen.queryByText("instructionsPanel.removeInstructions"), ).not.toBeInTheDocument(); }); diff --git a/src/utils/i18n.js b/src/utils/i18n.js index a10f5c3c5..554844233 100644 --- a/src/utils/i18n.js +++ b/src/utils/i18n.js @@ -208,7 +208,6 @@ i18n instructionsPanel: { emptyState: { addInstructions: "Add instructions", - removeInstructions: "Remove instructions", edits: "Like project code, students will not see any edits you make to the instructions after they have saved their version of the project.", location: @@ -217,6 +216,7 @@ i18n purpose: "Instructions can be added to your project to guide students.", }, + removeInstructions: "Remove instructions", nextStep: "Next step", previousStep: "Previous step", projectSteps: "Project steps", From 8cc598644ae8c826c62dff7a3e2cbe0309696345 Mon Sep 17 00:00:00 2001 From: Jamie Benstead Date: Thu, 30 Jan 2025 15:37:13 +0000 Subject: [PATCH 21/21] Fix linting issue --- .../Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js index 123544f62..167af5152 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js @@ -83,9 +83,7 @@ describe("When instructionsEditable is true", () => { }); test("Remove instructions modal is opened", () => { - const button = screen.queryByText( - "instructionsPanel.removeInstructions", - ); + const button = screen.queryByText("instructionsPanel.removeInstructions"); fireEvent.click(button); expect(