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

Feat/editable instructions #1166

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ jobs:
REACT_APP_PLAUSIBLE_SOURCE: ""

- name: Archive cypress artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4.6.0
if: failure()
with:
name: cypress-artifacts
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Added

- Autosave instructions
- Editable instructions
- 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)

### Changed

- Made `INSTRUCTIONS.md` a reserved file name (#1160)
- Clear the redux store when the component unmounts (#1169)

## [0.28.14] - 2025-01-06

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"js-convert-case": "^4.2.0",
"jszip": "^3.10.1",
"jszip-utils": "^0.1.0",
"marked": "^15.0.6",
"material-symbols": "^0.27.0",
"mime-types": "^2.1.35",
"node-html-parser": "^6.1.5",
Expand All @@ -53,6 +54,7 @@
"prismjs": "^1.29.0",
"prompts": "2.4.0",
"prop-types": "^15.8.1",
"raw-loader": "^4.0.2",
"rc-resize-observer": "^1.3.1",
"re-resizable": "6.9.9",
"react": "^18.1.0",
Expand Down
20 changes: 20 additions & 0 deletions src/assets/markdown/demoInstructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# How instructions work

## Enabling instructions

Any text written here will be visible to students in the sidebar

If you decide you do not want instructions, simply leave this panel blank.

## Writing instructions

Write your instructions using [Markdown](https://www.markdownguide.org/)
### What you can do

Lists:

- Bullet points
- Bullet points

1. numbered steps
2. numbered steps
13 changes: 13 additions & 0 deletions src/assets/stylesheets/Instructions.scss
Original file line number Diff line number Diff line change
Expand Up @@ -255,4 +255,17 @@
white-space: pre-wrap;
}
}

.project-instructions__empty {
background-color: $rpf-teal-100;
border-radius: $space-0-5;
display: flex;
flex-direction: column;
gap: $space-1-5;
padding: $space-1;
}

.project-instructions__empty-text {
margin: 0;
}
}
104 changes: 95 additions & 9 deletions src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useRef, useMemo, useState } from "react";
import SidebarPanel from "../SidebarPanel";
import { useTranslation } from "react-i18next";
import { Trans, useTranslation } from "react-i18next";
import { useSelector, useDispatch } from "react-redux";
import ProgressBar from "./ProgressBar/ProgressBar";
import "../../../../assets/stylesheets/Instructions.scss";
Expand All @@ -10,8 +10,16 @@ import "prismjs/plugins/line-numbers/prism-line-numbers.css";
import "prismjs/plugins/line-highlight/prism-line-highlight.css";
import { quizReadyEvent } from "../../../../events/WebComponentCustomEvents";
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(
(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();
Expand All @@ -28,9 +36,12 @@ const InstructionsPanel = () => {
}, [quiz]);

const numberOfSteps = useSelector(
(state) => state.instructions.project.steps.length,
(state) => state.instructions.project?.steps?.length || 0,
);

const hasInstructions = steps && steps.length > 0;
const hasMultipleSteps = numberOfSteps > 1;

const applySyntaxHighlighting = (container) => {
const codeElements = container.querySelectorAll(
".language-python, .language-html, .language-css",
Expand All @@ -54,17 +65,26 @@ const InstructionsPanel = () => {

useEffect(() => {
const setStepContent = (content) => {
stepContent.current.parentElement.scrollTo({ top: 0 });
stepContent.current.innerHTML = content;
applySyntaxHighlighting(stepContent.current);
if (stepContent.current) {
stepContent.current?.parentElement.scrollTo({ top: 0 });
stepContent.current.innerHTML = content;
applySyntaxHighlighting(stepContent.current);
}
};
if (isQuiz && !quizCompleted) {
setStepContent(quiz.questions[quiz.currentQuestion]);
document.dispatchEvent(quizReadyEvent);
} else if (steps[currentStepPosition]) {
} else if (hasInstructions && steps[currentStepPosition]) {
setStepContent(steps[currentStepPosition].content);
}
}, [steps, currentStepPosition, quiz, quizCompleted, isQuiz]);
}, [
hasInstructions,
steps,
currentStepPosition,
quiz,
quizCompleted,
isQuiz,
]);

useEffect(() => {
if (quizCompleted && isQuiz) {
Expand All @@ -76,13 +96,79 @@ const InstructionsPanel = () => {
}
}, [quizCompleted, currentStepPosition, numberOfSteps, dispatch, isQuiz]);

const addInstructions = () => {
dispatch(setProjectInstructions(demoInstructions));
};

const AddInstructionsButton = () => {
return (
<DesignSystemButton
className="btn--primary"
icon="add"
text={t("instructionsPanel.emptyState.addInstructions")}
onClick={addInstructions}
fill
textAlways
small
/>
);
};

const onChange = (e) => {
dispatch(setProjectInstructions(e.target.value));
};

return (
<SidebarPanel
defaultWidth="30vw"
heading={t("instructionsPanel.projectSteps")}
Footer={ProgressBar}
Button={instructionsEditable && !hasInstructions && AddInstructionsButton}
{...{ Footer: hasMultipleSteps && ProgressBar }}
>
<div className="project-instructions" ref={stepContent}></div>
<div className="project-instructions">
{instructionsEditable ? (
hasInstructions ? (
<div>
{instructionsEditable && (
<textarea
data-testid="instructionTextarea"
value={project.instructions}
onChange={onChange}
></textarea>
)}
</div>
) : (
<div className="project-instructions__empty">
<p className="project-instructions__empty-text">
{t("instructionsPanel.emptyState.purpose")}
</p>
<p className="project-instructions__empty-text">
{t("instructionsPanel.emptyState.location")}
</p>
<p className="project-instructions__empty-text">
<Trans
i18nKey="instructionsPanel.emptyState.markdown"
components={[
<Link
href="https://commonmark.org/help/"
target="_blank"
rel="noreferrer"
/>,
]}
/>
</p>
<p className="project-instructions__empty-text">
{t("instructionsPanel.emptyState.edits")}
</p>
</div>
)
) : (
<div
className="project-instructions__content"
ref={stepContent}
></div>
)}
</div>
</SidebarPanel>
);
};
Expand Down
Loading
Loading