Skip to content

Commit 77b2268

Browse files
authored
Added a save button to DownloadPanel (#872)
Allows mobile users to save their project and remix in place of auto remixing (#843)
1 parent 0ba2c54 commit 77b2268

File tree

5 files changed

+125
-2
lines changed

5 files changed

+125
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## Unreleased
88

9+
### Added
10+
11+
- Download panel save button (#872)
12+
913
### Changed
1014

1115
- Stack editor input and output panels based on container width (#869)

src/assets/stylesheets/DownloadPanel.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@
3535
box-sizing: border-box;
3636
margin: 0;
3737
}
38+
39+
.download-panel__download-section {
40+
margin-block-end: $space-1;
41+
}

src/components/Menus/Sidebar/DownloadPanel/DownloadPanel.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import { useTranslation } from "react-i18next";
33
import { useSelector } from "react-redux";
44
import SidebarPanel from "../SidebarPanel";
55
import DownloadButton from "../../../DownloadButton/DownloadButton";
6+
import SaveButton from "../../../SaveButton/SaveButton";
67
import DesignSystemButton from "../../../DesignSystemButton/DesignSystemButton";
78
import DownloadIcon from "../../../../assets/icons/download.svg";
9+
import { isOwner } from "../../../../utils/projectHelpers";
810

911
import {
1012
logInEvent,
@@ -15,6 +17,8 @@ import "../../../../assets/stylesheets/DownloadPanel.scss";
1517
export const DownloadPanel = () => {
1618
const { t } = useTranslation();
1719
const user = useSelector((state) => state.auth.user);
20+
const project = useSelector((state) => state.editor.project);
21+
const projectOwner = isOwner(user, project);
1822

1923
const handleLogIn = () => {
2024
if (window.plausible) {
@@ -64,6 +68,7 @@ export const DownloadPanel = () => {
6468
fill
6569
/>
6670
</div>
71+
{user && !projectOwner && <SaveButton fill />}
6772
</SidebarPanel>
6873
);
6974
};

src/components/Menus/Sidebar/DownloadPanel/DownloadPanel.test.js

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ describe("DownloadPanel", () => {
113113
).toBeInTheDocument();
114114
});
115115

116+
test("Does not render the save button", () => {
117+
expect(container.queryByText("header.save")).not.toBeInTheDocument();
118+
});
119+
116120
test("The download button initiates a download", async () => {
117121
const webComponentDownloadButton = screen.getByText(
118122
"downloadPanel.downloadButton",
@@ -122,7 +126,7 @@ describe("DownloadPanel", () => {
122126
});
123127
});
124128

125-
describe("When logged in", () => {
129+
describe("When logged in and not the project owner", () => {
126130
beforeEach(() => {
127131
const middlewares = [];
128132
const mockStore = configureStore(middlewares);
@@ -144,10 +148,14 @@ describe("DownloadPanel", () => {
144148
},
145149
],
146150
},
151+
loading: "success",
147152
},
148153
auth: {
149154
user: {
150155
access_token: "39a09671-be55-4847-baf5-8919a0c24a25",
156+
profile: {
157+
user: "some-user-id",
158+
},
151159
},
152160
},
153161
};
@@ -201,6 +209,107 @@ describe("DownloadPanel", () => {
201209
).toBeInTheDocument();
202210
});
203211

212+
test("Renders the save button", () => {
213+
expect(container.getByText("header.save")).toBeInTheDocument();
214+
});
215+
216+
test("The download button initiates a download", async () => {
217+
const webComponentDownloadButton = screen.getByText(
218+
"downloadPanel.downloadButton",
219+
).parentElement;
220+
fireEvent.click(webComponentDownloadButton);
221+
await waitFor(() => expect(FileSaver.saveAs).toHaveBeenCalled());
222+
});
223+
});
224+
225+
describe("When logged in and the project owner", () => {
226+
beforeEach(() => {
227+
const middlewares = [];
228+
const mockStore = configureStore(middlewares);
229+
const initialState = {
230+
editor: {
231+
project: {
232+
name: "My epic project",
233+
identifier: "hello-world-project",
234+
components: [
235+
{
236+
name: "main",
237+
extension: "py",
238+
content: "print('hello world')",
239+
},
240+
],
241+
image_list: [
242+
{
243+
url: "a.com/b",
244+
},
245+
],
246+
user_id: "some-user-id",
247+
},
248+
loading: "success",
249+
},
250+
auth: {
251+
user: {
252+
access_token: "39a09671-be55-4847-baf5-8919a0c24a25",
253+
profile: {
254+
user: "some-user-id",
255+
},
256+
},
257+
},
258+
};
259+
const store = mockStore(initialState);
260+
261+
container = render(
262+
<Provider store={store}>
263+
<MemoryRouter>
264+
<DownloadPanel />
265+
</MemoryRouter>
266+
</Provider>,
267+
);
268+
});
269+
test("Renders the correct heading", () => {
270+
expect(container.getByText("downloadPanel.heading")).toBeInTheDocument();
271+
});
272+
273+
test("Does not render the log-in subtitle", () => {
274+
expect(
275+
container.queryByText("downloadPanel.logInTitle"),
276+
).not.toBeInTheDocument();
277+
});
278+
279+
test("Does not render the log-in hint", () => {
280+
expect(
281+
container.queryByText("downloadPanel.logInHint"),
282+
).not.toBeInTheDocument();
283+
});
284+
285+
test("Does not render the log-in button", () => {
286+
expect(
287+
container.queryByText("downloadPanel.logInButton"),
288+
).not.toBeInTheDocument();
289+
});
290+
291+
test("Does not render the sign-up button", () => {
292+
expect(
293+
container.queryByText("downloadPanel.signUpButton"),
294+
).not.toBeInTheDocument();
295+
});
296+
297+
test("Renders the download hint", () => {
298+
expect(
299+
container.getByText("downloadPanel.downloadHint"),
300+
).toBeInTheDocument();
301+
});
302+
303+
test("Renders the download button", () => {
304+
expect(
305+
container.getByText("downloadPanel.downloadButton"),
306+
).toBeInTheDocument();
307+
});
308+
309+
test("Does not render the save button", () => {
310+
expect(container.queryByText("header.save")).not.toBeInTheDocument();
311+
});
312+
204313
test("The download button initiates a download", async () => {
205314
const webComponentDownloadButton = screen.getByText(
206315
"downloadPanel.downloadButton",

src/components/SaveButton/SaveButton.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import DesignSystemButton from "../DesignSystemButton/DesignSystemButton";
1010
import SaveIcon from "../../assets/icons/save.svg";
1111
import { triggerSave } from "../../redux/EditorSlice";
1212

13-
const SaveButton = ({ className, type }) => {
13+
const SaveButton = ({ className, type, fill = false }) => {
1414
const dispatch = useDispatch();
1515
const { t } = useTranslation();
1616

@@ -51,6 +51,7 @@ const SaveButton = ({ className, type }) => {
5151
textAlways
5252
icon={<SaveIcon />}
5353
type={buttonType}
54+
fill={fill}
5455
/>
5556
)
5657
);

0 commit comments

Comments
 (0)