Skip to content
Merged
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
76 changes: 75 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,97 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Added

- `assets_identifier` attribute for the web component (#901)
- `code` attribute overriding content of `main.py`/`index.html` in the web component (#901)

## [0.21.2] - 2024-01-23

### Changed

- Minor copy changes to HTML add file modal
- Toggle errors sent via apiCallHandler off (#890)
- Upgrade webpack-dev-server to 4.0.0 to support conditional headers
- Upgrade yarn to 3.4.1 to workaround a string-width issue

### Fixed

- Editor input not focussing on iPad (#898)

## [0.21.1] - 2024-01-11

### Added

- Download panel save button (#872)

### Changed

- Stack editor input and output panels based on container width (#869)
- Blob/URL replacement in HTMLRunner (#877)

### Fixed

- Boolean web component attributes (#881)
- Wrap the project bar when sidebar is wide (#869)
- Web component project bar state update delay (#869)
- Left border of the project bar (#869)
- Indentation of code block first line (#883)
- Code block 3-digit line numbers (#883)

## [0.21.0] - 2024-01-05

### Added

- Load remix functionality (#804)
- ProjectBar functionality in the web component (#799)
- WebComponent can receive style strings from host app (#811)
- Quiz rendering in InstructionsPanel (#777)
- Styling for the task section of the instructions (#781)
- Styling for the instructions callouts (#788)
- Output styles for Instructions (#790, #807)
- Styling for the instructions code snippets (#795)
- Styling for the instructions code blocks (#794)
- Styling for the instructions code blocks (#794, #808)
- quizReady custom event (#812)
- Code snippet and code block syntax highlighting for HTML and CSS in the instructions (#824)
- Toast save reminder to web component (#822)
- ProgressBar and completion-handling on quizzes (#834)
- Support for multiple host styles in the web component (#863)

### Changed

- Untangle HTML runner (#876)
- Project sidebar mobile structure and default to instructions behaviour (#823)
- Auth web component from user in local storage (#852)
- Save and download panel copy (#784)
- Application of styles in the web component to remove `sass-to-string` (#788)
- Info panel links open in a new tab (#803)
- Copy updates (#803)
- Restyling the instructions progress bar (#808)

## Fixed

- Standalone editor height (#864)
- Web component height on Firefox (#838)
- Web component resizable handle errors & sidebar width (#806)
- HTML projects loading in web component (#789)
- Enabled modals in the web component (#802)
- Context menu styling in the web component (#819)
- Collect web component login data (#818)
- Syntax highlighting contrast in dark mode (#824)
- Secondary button theming (#827)
- Instructions step buttons hit area (#827)
- Instructions code block line highlighting (#827)
- Instructions image widths (#827)
- Save status spacing (#827)
- Disappearing borders on tablet (#827)
- Dark mode button theming (#850)
- `<strong>` styling on Firefox (#854)
- Progress bar width on Firefox (#855)
- Instructions blocks spacing (#856)
- Font family stacking (#857)
- Save/download panel spacing (#859)
- Instructions output wrapping in Firefox (#862)
- Instructions code blocks with no line numbers (#863)
- New file button width (#865)

## [0.20.0] - 2023-11-24

Expand Down
2 changes: 1 addition & 1 deletion src/components/Editor/EditorPanel/EditorPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => {
useEffect(() => {
const code = project.components.find(
(item) => item.extension === extension && item.name === fileName,
).content;
)?.content;
const mode = getMode();
const startState = EditorState.create({
doc: code,
Expand Down
2 changes: 1 addition & 1 deletion src/components/ExternalFiles/ExternalFiles.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const ExternalFiles = () => {

return (
<div id="file-content" hidden>
{project.components.map((file, i) => {
{project.components?.map((file, i) => {
if (["csv", "txt"].includes(file.extension)) {
return (
<div id={`${file.name}.${file.extension}`} key={i}>
Expand Down
10 changes: 9 additions & 1 deletion src/containers/WebComponentLoader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import {
disableTheming,
setEmbedded,
setSenseHatAlwaysEnabled,
stopCodeRun,
stopDraw,
Expand All @@ -20,6 +21,7 @@ import { useCookies } from "react-cookie";
const WebComponentLoader = (props) => {
const loading = useSelector((state) => state.editor.loading);
const {
assetsIdentifier,
authKey,
identifier,
code,
Expand All @@ -41,6 +43,7 @@ const WebComponentLoader = (props) => {
(state) => state.editor.hasShownSavePrompt,
);
const saveTriggered = useSelector((state) => state.editor.saveTriggered);
const isEmbedded = useSelector((state) => state.editor.isEmbedded);

const [cookies, setCookie] = useCookies(["theme", "fontSize"]);
const themeDefault = window.matchMedia("(prefers-color-scheme:dark)").matches
Expand Down Expand Up @@ -85,8 +88,13 @@ const WebComponentLoader = (props) => {
}
}, [loading, project]);

if (embedded !== isEmbedded) {
dispatch(setEmbedded(embedded));
}

useProject({
projectIdentifier: projectIdentifier,
assetsIdentifier,
projectIdentifier,
code,
accessToken: user && user.access_token,
});
Expand Down
48 changes: 48 additions & 0 deletions src/hooks/useProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { defaultPythonProject } from "../utils/defaultProjects";
import { useTranslation } from "react-i18next";

export const useProject = ({
assetsIdentifier = null,
projectIdentifier = null,
code = null,
accessToken = null,
}) => {
const project = useSelector((state) => state.editor.project);
const loading = useSelector((state) => state.editor.loading);
const isEmbedded = useSelector((state) => state.editor.isEmbedded);
const isBrowserPreview = useSelector((state) => state.editor.browserPreview);
const getCachedProject = (id) =>
Expand Down Expand Up @@ -42,6 +45,18 @@ export const useProject = ({
return;
}

if (assetsIdentifier) {
dispatch(
syncProject("load")({
identifier: assetsIdentifier,
locale: i18n.language,
accessToken,
assetsOnly: true,
}),
);
return;
}

if (projectIdentifier) {
dispatch(
syncProject("load")({
Expand All @@ -66,4 +81,37 @@ export const useProject = ({
const data = defaultPythonProject;
dispatch(setProject(data));
}, [projectIdentifier, cachedProject, i18n.language, accessToken]);

useEffect(() => {
if (code && loading === "success") {
const defaultName = project.project_type === "html" ? "index" : "main";
const defaultExtension = project.project_type === "html" ? "html" : "py";

const mainComponent = project.components?.find(
(component) =>
component.name === defaultName &&
component.extension === defaultExtension,
) || { name: defaultName, extension: defaultExtension, content: "" };

const otherComponents =
project.components?.filter(
(component) =>
component.name !== defaultName &&
component.extension !== defaultExtension,
) || [];

const updatedProject = {
...project,
project_type: project.project_type || "python",
components: [
...otherComponents,
{
...mainComponent,
content: code,
},
],
};
dispatch(setProject(updatedProject));
}
}, [code, loading]);
};
9 changes: 7 additions & 2 deletions src/redux/EditorSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,24 @@ import {
createRemix,
deleteProject,
readProjectList,
loadAssets,
} from "../utils/apiCallHandler";

export const syncProject = (actionName) =>
createAsyncThunk(
`editor/${actionName}Project`,
async (
{ project, identifier, locale, accessToken, autosave },
{ project, identifier, locale, accessToken, autosave, assetsOnly },
{ rejectWithValue },
) => {
let response;
switch (actionName) {
case "load":
response = await readProject(identifier, locale, accessToken);
if (assetsOnly) {
response = await loadAssets(identifier, locale, accessToken);
} else {
response = await readProject(identifier, locale, accessToken);
}
break;
case "remix":
response = await createRemix(project, accessToken);
Expand Down
8 changes: 8 additions & 0 deletions src/utils/apiCallHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ export const readProject = async (projectIdentifier, locale, accessToken) => {
);
};

export const loadAssets = async (assetsIdentifier, locale, accessToken) => {
const queryString = locale ? `?locale=${locale}` : "";
return await get(
`${host}/api/projects/${assetsIdentifier}/images${queryString}`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unfamiliar with the admin side, will the query string ever change the assets returned? e.g. can we have a Spanish locale image vs an English locale image?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess potentially, maybe if there's text in there or something, or they want to provide a more culturally relevant image? The locale query string makes it load up a different project anyway, so it is possible to do this.

headers(accessToken),
);
};

export const readProjectList = async (page, accessToken) => {
return await get(`${host}/api/projects`, {
params: { page },
Expand Down
5 changes: 4 additions & 1 deletion src/web-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class WebComponent extends HTMLElement {

static get observedAttributes() {
return [
"assets_identifier",
"auth_key",
"identifier",
"code",
Expand All @@ -52,7 +53,9 @@ class WebComponent extends HTMLElement {
attributeChangedCallback(name, _oldVal, newVal) {
let value;

if (["sense_hat_always_enabled", "with_sidebar"].includes(name)) {
if (
["sense_hat_always_enabled", "with_sidebar", "embedded"].includes(name)
) {
value = newVal === "true";
} else if (["instructions", "sidebar_options"].includes(name)) {
value = JSON.parse(newVal);
Expand Down