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
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ yarn exec cypress open
`src/assets/stylesheets/InternalStyles.scss` (and external libs in
`ExternalStyles.scss`); avoid `rem`, prefer `em` and `--scale-factor`.

## Translations / locale files
- Only update `public/translations/en.json` when adding or changing translation keys.
- Do not add or edit keys in other locale files under `public/translations/`; Crowdin sync overwrites them.
- Missing keys in non-en locales are intentional until Crowdin syncs; the app falls back to `en`. Do not suggest adding a new key to other locale files when it appears only in `en.json`.

## Git & Commit Guidance
- Write descriptive commit messages that explain why a change was made.
- When applicable, include alternatives considered and why they were not
Expand Down
24 changes: 14 additions & 10 deletions cypress/helpers/editor.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
export const getEditorShadow = () => cy.get("editor-wc").shadow();

export const getSidebarPanel = () =>
getEditorShadow().findByTestId("sidebar__panel");

export const openSaveAndDownloadPanel = () => {
getEditorShadow().findByRole("button", { name: "Download project" }).click();
getEditorShadow()

getSidebarPanel()
.findByRole("heading", { name: "Save & download" })
.should("be.visible");

return {
uploadProject: (fixturePath) => {
getEditorShadow()
.find(".download-panel__download-section")
.findByRole("button", { name: "Upload project" })
.should("be.visible");
getEditorShadow()
.findByTestId("upload-file-input")
.selectFile(fixturePath, { force: true });
getSidebarPanel().within(() => {
cy.findByRole("button", { name: "Upload project" }).should(
"be.visible",
);
cy.findByTestId("upload-file-input").selectFile(fixturePath, {
force: true,
});
});
},
downloadProject: () => {
getEditorShadow()
.find(".download-panel__download-section")
getSidebarPanel()
.findByRole("button", { name: "Download project" })
.click();
},
Expand Down
1 change: 1 addition & 0 deletions public/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
},
"header": {
"download": "Download",
"upload": "Upload",
"downloadFileNameDefault": "my {{project_type}} project",
"editorLogoAltText": "Editor logo",
"projects": "Your projects",
Expand Down
8 changes: 7 additions & 1 deletion src/components/Editor/Project/Project.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "../../../assets/stylesheets/Project.scss";
import Output from "../Output/Output";
import { showSavedMessage } from "../../../utils/Notifications";
import ProjectBar from "../../ProjectBar/ProjectBar";
import ScratchProjectBar from "../../ProjectBar/ScratchProjectBar";
import Sidebar from "../../Menus/Sidebar/Sidebar";
import EditorInput from "../EditorInput/EditorInput";
import ResizableWithHandle from "../../../utils/ResizableWithHandle";
Expand Down Expand Up @@ -76,7 +77,12 @@ const Project = (props) => {
/>
)}
<div className="project-wrapper" ref={containerRef}>
{withProjectbar && <ProjectBar nameEditable={nameEditable} />}
{withProjectbar &&
(isCodeEditorScratchProject ? (
<ScratchProjectBar nameEditable={nameEditable} />
) : (
<ProjectBar nameEditable={nameEditable} />
))}
{!loading && !isCodeEditorScratchProject && (
<div className="proj-editor-wrapper">
<ResizableWithHandle
Expand Down
50 changes: 50 additions & 0 deletions src/components/Editor/Project/Project.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,56 @@ test("Renders sidebar with correct options if withSidebar is true", () => {
expect(screen.queryByTitle("sidebar.settings")).toBeInTheDocument();
});

describe("Project bar selection", () => {
test("renders default project bar for non-scratch projects", () => {
const middlewares = [];
const mockStore = configureStore(middlewares);
const initialState = {
editor: {
project: { components: [], project_type: "python" },
loading: "success",
openFiles: [[]],
focussedFileIndices: [0],
},
auth: {},
};
const store = mockStore(initialState);
render(
<Provider store={store}>
<MemoryRouter>
<Project />
</MemoryRouter>
</Provider>,
);
expect(screen.getByTestId("default-project-bar")).toBeInTheDocument();
expect(screen.queryByTestId("scratch-project-bar")).not.toBeInTheDocument();
});

test("renders scratch project bar for code_editor_scratch projects", () => {
const middlewares = [];
const mockStore = configureStore(middlewares);
const initialState = {
editor: {
project: { components: [], project_type: "code_editor_scratch" },
loading: "success",
openFiles: [[]],
focussedFileIndices: [0],
},
auth: {},
};
const store = mockStore(initialState);
render(
<Provider store={store}>
<MemoryRouter>
<Project />
</MemoryRouter>
</Provider>,
);
expect(screen.getByTestId("scratch-project-bar")).toBeInTheDocument();
expect(screen.queryByTestId("default-project-bar")).not.toBeInTheDocument();
});
});

test("Renders container for scratch projects", () => {
const middlewares = [];
const mockStore = configureStore(middlewares);
Expand Down
33 changes: 3 additions & 30 deletions src/components/ProjectBar/ProjectBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,29 @@ import { useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import SaveStatus from "../SaveStatus/SaveStatus";
import DownloadIcon from "../../assets/icons/download.svg";
import SaveIcon from "../../assets/icons/save.svg";
import ProjectName from "../ProjectName/ProjectName";
import DownloadButton from "../DownloadButton/DownloadButton";
import SaveButton from "../SaveButton/SaveButton";
import DesignSystemButton from "../DesignSystemButton/DesignSystemButton";

import "../../assets/stylesheets/ProjectBar.scss";
import { isOwner } from "../../utils/projectHelpers";
import { useScratchSaveState } from "../../hooks/useScratchSaveState";

const ProjectBar = ({ nameEditable = true }) => {
const { t } = useTranslation();

const project = useSelector((state) => state.editor.project);
const user = useSelector((state) => state.auth.user);
const loading = useSelector((state) => state.editor.loading);
const saving = useSelector((state) => state.editor.saving);
const lastSavedTime = useSelector((state) => state.editor.lastSavedTime);
const projectOwner = isOwner(user, project);
const readOnly = useSelector((state) => state.editor.readOnly);
const isScratchProject = project?.project_type === "code_editor_scratch";
const showScratchSaveButton = Boolean(isScratchProject && user && !readOnly);
const enableScratchSaveState = Boolean(
loading === "success" && showScratchSaveButton,
);
const { isScratchSaving, saveScratchProject, scratchSaveLabelKey } =
useScratchSaveState({
enabled: enableScratchSaveState,
});
const scratchSaveLabel = t(scratchSaveLabelKey);

if (loading !== "success") {
return null;
}

return (
<div className="project-bar">
<div className="project-bar" data-testid="default-project-bar">
<ProjectName editable={!readOnly && nameEditable} isHeading={true} />
<div className="project-bar__right">
<div className="project-bar__btn-wrapper">
Expand All @@ -50,25 +36,12 @@ const ProjectBar = ({ nameEditable = true }) => {
type="tertiary"
/>
</div>
{!isScratchProject && !projectOwner && !readOnly && (
{!projectOwner && !readOnly && (
<div className="project-bar__btn-wrapper">
<SaveButton className="project-bar__btn btn--save" />
</div>
)}
{showScratchSaveButton && (
<div className="project-bar__btn-wrapper">
<DesignSystemButton
className="project-bar__btn btn--save btn--primary"
onClick={saveScratchProject}
text={scratchSaveLabel}
textAlways
icon={<SaveIcon />}
type="primary"
disabled={isScratchSaving}
/>
</div>
)}
{lastSavedTime && user && !readOnly && !isScratchProject && (
{lastSavedTime && user && !readOnly && (
<SaveStatus saving={saving} lastSavedTime={lastSavedTime} />
)}
</div>
Expand Down
Loading
Loading