From 4d0dd180ed0762fc48707f1e6ef7630b6512ac72 Mon Sep 17 00:00:00 2001 From: Conor Date: Wed, 23 Oct 2024 16:04:04 +0100 Subject: [PATCH 01/22] move REACT_APP_API_ENDPOINT and REACT_APP_AUTHENTICATION_URL out of files --- src/app/store.js | 3 +- .../ImageUploadButton/ImageUploadButton.jsx | 4 +- .../PyodideRunner/PyodideRunner.jsx | 4 +- .../SkulptRunner/SkulptRunner.jsx | 4 +- src/components/Login/LogoutButton.jsx | 7 +- src/components/Modals/NewProjectModal.jsx | 6 +- src/components/Modals/NewProjectModal.test.js | 5 +- src/hooks/useProject.test.js | 2 +- src/redux/EditorSlice.js | 6 +- src/redux/EditorSlice.test.js | 18 +- .../reducers/loadProjectReducers.test.js | 3 +- src/utils/apiCallHandler.js | 230 ++++++++++-------- src/utils/apiCallHandler.test.js | 6 +- src/utils/login.js | 3 +- src/utils/userManager.js | 26 +- 15 files changed, 190 insertions(+), 137 deletions(-) diff --git a/src/app/store.js b/src/app/store.js index aa1691a78..86e76a03e 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -2,8 +2,9 @@ import { configureStore } from "@reduxjs/toolkit"; import EditorReducer from "../redux/EditorSlice"; import InstructionsReducer from "../redux/InstructionsSlice"; import { reducer, loadUser } from "redux-oidc"; -import userManager from "../utils/userManager"; +import UserManager from "../utils/userManager"; +const userManager = UserManager({ reactAppAuthenticationUrl: "TODO" }); const store = configureStore({ reducer: { editor: EditorReducer, diff --git a/src/components/Editor/ImageUploadButton/ImageUploadButton.jsx b/src/components/Editor/ImageUploadButton/ImageUploadButton.jsx index 11ac2fdf4..c7c0552e1 100644 --- a/src/components/Editor/ImageUploadButton/ImageUploadButton.jsx +++ b/src/components/Editor/ImageUploadButton/ImageUploadButton.jsx @@ -9,7 +9,9 @@ import { updateImages, setNameError } from "../../../redux/EditorSlice"; import Button from "../../Button/Button"; import NameErrorMessage from "../ErrorMessage/NameErrorMessage"; import store from "../../../app/store"; -import { uploadImages } from "../../../utils/apiCallHandler"; +import ApiCallHandler from "../../../utils/apiCallHandler"; + +const { uploadImages } = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); const allowedExtensions = { python: ["jpg", "jpeg", "png", "gif"], diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index 8bb232feb..658da2ea0 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -12,12 +12,14 @@ import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { useMediaQuery } from "react-responsive"; import { MOBILE_MEDIA_QUERY } from "../../../../../utils/mediaQueryBreakpoints"; import ErrorMessage from "../../../ErrorMessage/ErrorMessage"; -import { createError } from "../../../../../utils/apiCallHandler"; +import ApiCallHandler from "../../../../../utils/apiCallHandler"; import VisualOutputPane from "./VisualOutputPane"; import OutputViewToggle from "../OutputViewToggle"; import { SettingsContext } from "../../../../../utils/settings"; import RunnerControls from "../../../../RunButton/RunnerControls"; +const { createError } = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); + const getWorkerURL = (url) => { const content = ` /* global PyodideWorker */ diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx index dc852989c..ae9e79334 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx @@ -15,7 +15,7 @@ import { triggerDraw, } from "../../../../../redux/EditorSlice"; import ErrorMessage from "../../../ErrorMessage/ErrorMessage"; -import { createError } from "../../../../../utils/apiCallHandler"; +import ApiCallHandler from "../../../../../utils/apiCallHandler"; import store from "../../../../../redux/stores/WebComponentStore"; import VisualOutputPane from "../VisualOutputPane"; import OutputViewToggle from "../OutputViewToggle"; @@ -24,6 +24,8 @@ import RunnerControls from "../../../../RunButton/RunnerControls"; import { MOBILE_MEDIA_QUERY } from "../../../../../utils/mediaQueryBreakpoints"; import classNames from "classnames"; +const { createError } = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); + const externalLibraries = { "./pygal/__init__.js": { path: `${process.env.ASSETS_URL}/shims/pygal/pygal.js`, diff --git a/src/components/Login/LogoutButton.jsx b/src/components/Login/LogoutButton.jsx index 8eab9d1b5..6dee6172b 100644 --- a/src/components/Login/LogoutButton.jsx +++ b/src/components/Login/LogoutButton.jsx @@ -1,15 +1,18 @@ import React from "react"; -import userManager from "../../utils/userManager"; +import UserManager from "../../utils/userManager"; import { useTranslation } from "react-i18next"; import Button from "../Button/Button"; import PropTypes from "prop-types"; const LogoutButton = ({ className, user }) => { const { t } = useTranslation(); + const userManager = UserManager({ reactAppAuthenticationUrl: "TODO" }); const onLogoutButtonClick = async (event) => { event.preventDefault(); - userManager.signoutRedirect({ id_token_hint: user?.id_token }); + userManager.signoutRedirect({ + id_token_hint: user?.id_token, + }); await userManager.removeUser(); localStorage.clear(); }; diff --git a/src/components/Modals/NewProjectModal.jsx b/src/components/Modals/NewProjectModal.jsx index c80ddc64f..27878ee29 100644 --- a/src/components/Modals/NewProjectModal.jsx +++ b/src/components/Modals/NewProjectModal.jsx @@ -5,12 +5,16 @@ import { closeNewProjectModal } from "../../redux/EditorSlice"; import { useDispatch, useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import InputModal from "./InputModal"; -import { createOrUpdateProject } from "../../utils/apiCallHandler"; +import ApiCallHandler from "../../utils/apiCallHandler"; import { useNavigate } from "react-router-dom"; import { DEFAULT_PROJECTS } from "../../utils/defaultProjects"; import HTMLIcon from "../../assets/icons/html.svg"; import PythonIcon from "../../assets/icons/python.svg"; +const { createOrUpdateProject } = ApiCallHandler({ + reactAppApiEndpoint: "TODO", +}); + const NewProjectModal = () => { const { t, i18n } = useTranslation(); const dispatch = useDispatch(); diff --git a/src/components/Modals/NewProjectModal.test.js b/src/components/Modals/NewProjectModal.test.js index 22dfb8ac8..131a0e414 100644 --- a/src/components/Modals/NewProjectModal.test.js +++ b/src/components/Modals/NewProjectModal.test.js @@ -4,12 +4,15 @@ import configureStore from "redux-mock-store"; import NewProjectModal from "./NewProjectModal"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; -import { createOrUpdateProject } from "../../utils/apiCallHandler"; +import ApiCallHandler from "../../utils/apiCallHandler"; import { defaultHtmlProject, defaultPythonProject, } from "../../utils/defaultProjects"; +const { createOrUpdateProject } = ApiCallHandler({ + reactAppApiEndpoint: "TODO", +}); const mockNavigate = jest.fn(); jest.mock("react-router", () => ({ diff --git a/src/hooks/useProject.test.js b/src/hooks/useProject.test.js index 3e32e506b..b3c79bea9 100644 --- a/src/hooks/useProject.test.js +++ b/src/hooks/useProject.test.js @@ -15,7 +15,7 @@ const loadProject = jest.fn(); jest.mock("../redux/EditorSlice"); -jest.mock("../utils/apiCallHandler", () => ({ +jest.mock("../utils/apiCallHandler", () => () => ({ readProject: async (identifier, projectType) => Promise.resolve({ data: { identifier: identifier, project_type: projectType }, diff --git a/src/redux/EditorSlice.js b/src/redux/EditorSlice.js index 8a73f4cc5..9a9a5387c 100644 --- a/src/redux/EditorSlice.js +++ b/src/redux/EditorSlice.js @@ -5,7 +5,9 @@ import { loadProjectFulfilled, loadProjectRejected, } from "./reducers/loadProjectReducers"; -import { +import ApiCallHandler from "../utils/apiCallHandler"; + +const { createOrUpdateProject, readProject, loadRemix, @@ -13,7 +15,7 @@ import { deleteProject, readProjectList, loadAssets, -} from "../utils/apiCallHandler"; +} = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); export const syncProject = (actionName) => createAsyncThunk( diff --git a/src/redux/EditorSlice.test.js b/src/redux/EditorSlice.test.js index 4d92af974..1b93feb5a 100644 --- a/src/redux/EditorSlice.test.js +++ b/src/redux/EditorSlice.test.js @@ -1,11 +1,4 @@ -import { - createOrUpdateProject, - createRemix, - deleteProject, - loadAssets, - readProject, - readProjectList, -} from "../utils/apiCallHandler"; +import ApiCallHandler from "../utils/apiCallHandler"; import reducer, { syncProject, @@ -23,6 +16,15 @@ import reducer, { setReadOnly, } from "./EditorSlice"; +const { + createOrUpdateProject, + createRemix, + deleteProject, + loadAssets, + readProject, + readProjectList, +} = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); + jest.mock("../utils/apiCallHandler"); test("Action stopCodeRun sets codeRunStopped to true", () => { diff --git a/src/redux/reducers/loadProjectReducers.test.js b/src/redux/reducers/loadProjectReducers.test.js index 62ba8ad68..177ef374e 100644 --- a/src/redux/reducers/loadProjectReducers.test.js +++ b/src/redux/reducers/loadProjectReducers.test.js @@ -1,11 +1,12 @@ import produce from "immer"; -import { readProject } from "../../utils/apiCallHandler"; +import ApiCallHandler from "../../utils/apiCallHandler"; import reducer, { syncProject } from "../../redux/EditorSlice"; import { loadProjectRejected } from "./loadProjectReducers"; jest.mock("../../utils/apiCallHandler"); +const { readProject } = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); const requestingAProject = function (project, projectFile) { const dispatch = jest.fn(); const initialState = { diff --git a/src/utils/apiCallHandler.js b/src/utils/apiCallHandler.js index 3294ab673..ead3394db 100644 --- a/src/utils/apiCallHandler.js +++ b/src/utils/apiCallHandler.js @@ -1,124 +1,144 @@ import axios from "axios"; import omit from "lodash/omit"; -const host = process.env.REACT_APP_API_ENDPOINT; - -const get = async (url, headers) => { - return await axios.get(url, headers); -}; - -const post = async (url, body, headers) => { - return await axios.post(url, body, headers); -}; +const ApiCallHandler = ({ reactAppApiEndpoint }) => { + const host = reactAppApiEndpoint; + + const get = async (url, headers) => { + return await axios.get(url, headers); + }; + + const post = async (url, body, headers) => { + return await axios.post(url, body, headers); + }; + + const put = async (url, body, headers) => { + return await axios.put(url, body, headers); + }; + + const headers = (accessToken) => { + let headersHash; + if (accessToken) { + headersHash = { Accept: "application/json", Authorization: accessToken }; + } else { + headersHash = { Accept: "application/json" }; + } + return { headers: headersHash }; + }; + + const createOrUpdateProject = async (projectWithUserId, accessToken) => { + const project = omit(projectWithUserId, ["user_id"]); + if (!project.identifier) { + return await post( + `${host}/api/projects`, + { project }, + headers(accessToken), + ); + } else { + return await put( + `${host}/api/projects/${project.identifier}`, + { project }, + headers(accessToken), + ); + } + }; + + const deleteProject = async (identifier, accessToken) => { + return await axios.delete( + `${host}/api/projects/${identifier}`, + headers(accessToken), + ); + }; -const put = async (url, body, headers) => { - return await axios.put(url, body, headers); -}; + const getImage = async (url) => { + return await get(url, headers()); + }; -const headers = (accessToken) => { - let headersHash; - if (accessToken) { - headersHash = { Accept: "application/json", Authorization: accessToken }; - } else { - headersHash = { Accept: "application/json" }; - } - return { headers: headersHash }; -}; + const loadRemix = async (projectIdentifier, accessToken) => { + return await get( + `${host}/api/projects/${projectIdentifier}/remix`, + headers(accessToken), + ); + }; -export const createOrUpdateProject = async (projectWithUserId, accessToken) => { - const project = omit(projectWithUserId, ["user_id"]); - if (!project.identifier) { + const createRemix = async (project, accessToken) => { return await post( - `${host}/api/projects`, + `${host}/api/projects/${project.identifier}/remix`, { project }, headers(accessToken), ); - } else { - return await put( - `${host}/api/projects/${project.identifier}`, - { project }, + }; + + const readProject = async (projectIdentifier, locale, accessToken) => { + const queryString = locale ? `?locale=${locale}` : ""; + return await get( + `${host}/api/projects/${projectIdentifier}${queryString}`, headers(accessToken), ); - } -}; - -export const deleteProject = async (identifier, accessToken) => { - return await axios.delete( - `${host}/api/projects/${identifier}`, - headers(accessToken), - ); -}; + }; -export const getImage = async (url) => { - return await get(url, headers()); -}; - -export const loadRemix = async (projectIdentifier, accessToken) => { - return await get( - `${host}/api/projects/${projectIdentifier}/remix`, - headers(accessToken), - ); -}; - -export const createRemix = async (project, accessToken) => { - return await post( - `${host}/api/projects/${project.identifier}/remix`, - { project }, - headers(accessToken), - ); -}; - -export const readProject = async (projectIdentifier, locale, accessToken) => { - const queryString = locale ? `?locale=${locale}` : ""; - return await get( - `${host}/api/projects/${projectIdentifier}${queryString}`, - headers(accessToken), - ); -}; - -export const loadAssets = async (assetsIdentifier, locale, accessToken) => { - const queryString = locale ? `?locale=${locale}` : ""; - return await get( - `${host}/api/projects/${assetsIdentifier}/images${queryString}`, - headers(accessToken), - ); -}; + const loadAssets = async (assetsIdentifier, locale, accessToken) => { + const queryString = locale ? `?locale=${locale}` : ""; + return await get( + `${host}/api/projects/${assetsIdentifier}/images${queryString}`, + headers(accessToken), + ); + }; -export const readProjectList = async (page, accessToken) => { - return await get(`${host}/api/projects`, { - params: { page }, - ...headers(accessToken), - }); -}; + const readProjectList = async (page, accessToken) => { + return await get(`${host}/api/projects`, { + params: { page }, + ...headers(accessToken), + }); + }; -export const uploadImages = async (projectIdentifier, accessToken, images) => { - var formData = new FormData(); + const uploadImages = async (projectIdentifier, accessToken, images) => { + var formData = new FormData(); - images.forEach((image) => { - formData.append("images[]", image, image.name); - }); + images.forEach((image) => { + formData.append("images[]", image, image.name); + }); - return await post( - `${host}/api/projects/${projectIdentifier}/images`, - formData, - { ...headers(accessToken), "Content-Type": "multipart/form-data" }, - ); + return await post( + `${host}/api/projects/${projectIdentifier}/images`, + formData, + { ...headers(accessToken), "Content-Type": "multipart/form-data" }, + ); + }; + + const createError = async ( + projectIdentifier, + userId, + error, + sendError = false, + ) => { + if (!sendError) { + return; + } + const { errorMessage, errorType } = error; + return await post(`${host}/api/project_errors`, { + error: errorMessage, + error_type: errorType, + project_id: projectIdentifier, + user_id: userId, + }); + }; + + return { + get, + post, + put, + createOrUpdateProject, + deleteProject, + getImage, + loadRemix, + createRemix, + readProject, + loadAssets, + readProjectList, + uploadImages, + createError, + }; }; -export const createError = async ( - projectIdentifier, - userId, - error, - sendError = false, -) => { - if (!sendError) { - return; - } - const { errorMessage, errorType } = error; - return await post(`${host}/api/project_errors`, { - error: errorMessage, - error_type: errorType, - project_id: projectIdentifier, - user_id: userId, - }); -}; +export default ApiCallHandler; diff --git a/src/utils/apiCallHandler.test.js b/src/utils/apiCallHandler.test.js index 5b0206ea2..6ebc33d70 100644 --- a/src/utils/apiCallHandler.test.js +++ b/src/utils/apiCallHandler.test.js @@ -1,6 +1,8 @@ import axios from "axios"; -import { +import ApiCallHandler from "./apiCallHandler"; + +const { getImage, createOrUpdateProject, readProject, @@ -9,7 +11,7 @@ import { uploadImages, readProjectList, createError, -} from "./apiCallHandler"; +} = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); jest.mock("axios"); const host = process.env.REACT_APP_API_ENDPOINT; diff --git a/src/utils/login.js b/src/utils/login.js index aec78c36a..aabd23ef2 100644 --- a/src/utils/login.js +++ b/src/utils/login.js @@ -1,4 +1,5 @@ -import userManager from "./userManager"; +import UserManager from "./userManager"; +const userManager = UserManager({ reactAppAuthenticationUrl: "TODO" }); export const login = ({ project, diff --git a/src/utils/userManager.js b/src/utils/userManager.js index 59eefc9cd..210a56764 100644 --- a/src/utils/userManager.js +++ b/src/utils/userManager.js @@ -5,13 +5,13 @@ const host = `${window.location.protocol}//${window.location.hostname}${ window.location.port ? `:${window.location.port}` : "" }`; -const userManagerConfig = { +const userManagerConfig = ({ reactAppAuthenticationUrl }) => ({ client_id: process.env.REACT_APP_AUTHENTICATION_CLIENT_ID, redirect_uri: `${host}/auth/callback`, post_logout_redirect_uri: host, response_type: "code", scope: "openid email profile force-consent allow-u13-login roles", - authority: process.env.REACT_APP_AUTHENTICATION_URL, + authority: reactAppAuthenticationUrl, silent_redirect_uri: `${host}/auth/silent_renew`, automaticSilentRenew: true, filterProtocolClaims: false, @@ -22,13 +22,21 @@ const userManagerConfig = { brand: "editor", login_options: "v1_signup", }, -}; +}); -const userManager = createUserManager(userManagerConfig); +const UserManager = ({ reactAppAuthenticationUrl }) => { + const mgr = createUserManager( + userManagerConfig({ + reactAppAuthenticationUrl, + }), + ); -userManager.events.addAccessTokenExpired(() => { - // If the token has expired, trigger the silent renew process - userManager.signinSilent(); -}); + mgr.events.addAccessTokenExpired(() => { + // If the token has expired, trigger the silent renew process + UserManager.signinSilent(); + }); + + return mgr; +}; -export default userManager; +export default UserManager; From 948309ea9ce43ee8c8f5e3baa4388afaa9762938 Mon Sep 17 00:00:00 2001 From: Conor Date: Thu, 24 Oct 2024 12:05:21 +0100 Subject: [PATCH 02/22] add index file --- public/index.html | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 public/index.html diff --git a/public/index.html b/public/index.html new file mode 100644 index 000000000..1dbfdf1f4 --- /dev/null +++ b/public/index.html @@ -0,0 +1,4 @@ +

You may be looking for...

+ From ad2d0b04dd099cf9f11b09b07ec6f35b3285a6ac Mon Sep 17 00:00:00 2001 From: Conor Date: Thu, 24 Oct 2024 17:14:09 +0100 Subject: [PATCH 03/22] pass through reactAppApiEndpoint --- .eslintrc.json | 2 +- src/app/store.js | 2 +- .../DownloadButton/DownloadButton.jsx | 6 +-- .../ImageUploadButton/ImageUploadButton.jsx | 16 ++++--- .../PyodideRunner/PyodideRunner.jsx | 2 +- .../SkulptRunner/SkulptRunner.jsx | 2 +- src/components/Login/LogoutButton.jsx | 2 +- src/components/Modals/NewProjectModal.jsx | 2 +- src/components/Modals/NewProjectModal.test.js | 2 +- src/containers/WebComponentLoader.jsx | 32 +++++++------- src/hooks/useProject.js | 19 +++++--- src/hooks/useProjectPersistence.js | 1 + src/redux/EditorSlice.js | 43 +++++++++++-------- src/redux/EditorSlice.test.js | 6 +-- .../reducers/loadProjectReducers.test.js | 2 +- src/utils/apiCallHandler.test.js | 2 +- src/utils/login.js | 6 +-- src/utils/userManager.js | 2 +- src/web-component.js | 39 +++++++++-------- 19 files changed, 104 insertions(+), 84 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 530cfa5e8..d12b94619 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -37,7 +37,7 @@ { "singleQuote": false, "jsxSingleQuote": false, - "trailingComma": "all", + "trailingComma": "es5", "semi": true, "bracketSpacing": true, "arrowParens": "always" diff --git a/src/app/store.js b/src/app/store.js index 86e76a03e..325418a8a 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -4,7 +4,7 @@ import InstructionsReducer from "../redux/InstructionsSlice"; import { reducer, loadUser } from "redux-oidc"; import UserManager from "../utils/userManager"; -const userManager = UserManager({ reactAppAuthenticationUrl: "TODO" }); +const userManager = UserManager({ reactAppAuthenticationUrl: "TODORAAU" }); const store = configureStore({ reducer: { editor: EditorReducer, diff --git a/src/components/DownloadButton/DownloadButton.jsx b/src/components/DownloadButton/DownloadButton.jsx index 4016ae54c..978e4d4e3 100644 --- a/src/components/DownloadButton/DownloadButton.jsx +++ b/src/components/DownloadButton/DownloadButton.jsx @@ -21,7 +21,7 @@ const DownloadButton = (props) => { const { t } = useTranslation(); const project = useSelector((state) => state.editor.project); const loginToSaveModalShowing = useSelector( - (state) => state.editor.loginToSaveModalShowing, + (state) => state.editor.loginToSaveModalShowing ); const dispatch = useDispatch(); @@ -62,8 +62,8 @@ const DownloadButton = (props) => { project.name || t("header.downloadFileNameDefault", { project_type: project.project_type, - }), - )}`, + }) + )}` ); }; diff --git a/src/components/Editor/ImageUploadButton/ImageUploadButton.jsx b/src/components/Editor/ImageUploadButton/ImageUploadButton.jsx index c7c0552e1..c90202739 100644 --- a/src/components/Editor/ImageUploadButton/ImageUploadButton.jsx +++ b/src/components/Editor/ImageUploadButton/ImageUploadButton.jsx @@ -11,8 +11,6 @@ import NameErrorMessage from "../ErrorMessage/NameErrorMessage"; import store from "../../../app/store"; import ApiCallHandler from "../../../utils/apiCallHandler"; -const { uploadImages } = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); - const allowedExtensions = { python: ["jpg", "jpeg", "png", "gif"], }; @@ -32,13 +30,13 @@ const allowedExtensionsString = (projectType) => { } }; -const ImageUploadButton = () => { +const ImageUploadButton = ({ reactAppApiEndpoint }) => { const [modalIsOpen, setIsOpen] = useState(false); const [files, setFiles] = useState([]); const dispatch = useDispatch(); const projectType = useSelector((state) => state.editor.project.project_type); const projectIdentifier = useSelector( - (state) => state.editor.project.identifier, + (state) => state.editor.project.identifier ); const projectImages = useSelector((state) => state.editor.project.image_list); const imageNames = projectImages.map((image) => `${image.filename}`); @@ -53,6 +51,10 @@ const ImageUploadButton = () => { setIsOpen(true); }; const saveImages = async () => { + const { uploadImages } = ApiCallHandler({ + reactAppApiEndpoint, + }); + files.every((file) => { const fileName = file.name; const extension = fileName.split(".").slice(1).join(".").toLowerCase(); @@ -67,8 +69,8 @@ const ImageUploadButton = () => { } else if (!allowedExtensions[projectType].includes(extension)) { dispatch( setNameError( - `Image names must end in ${allowedExtensionsString(projectType)}.`, - ), + `Image names must end in ${allowedExtensionsString(projectType)}.` + ) ); return false; } else { @@ -80,7 +82,7 @@ const ImageUploadButton = () => { const response = await uploadImages( projectIdentifier, user.access_token, - files, + files ); dispatch(updateImages(response.data.image_list)); closeModal(); diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index 658da2ea0..0eb0d5d4e 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -18,7 +18,7 @@ import OutputViewToggle from "../OutputViewToggle"; import { SettingsContext } from "../../../../../utils/settings"; import RunnerControls from "../../../../RunButton/RunnerControls"; -const { createError } = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); +const { createError } = ApiCallHandler({ reactAppApiEndpoint: "TODORAAE" }); const getWorkerURL = (url) => { const content = ` diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx index ae9e79334..32ac8a5f2 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx @@ -24,7 +24,7 @@ import RunnerControls from "../../../../RunButton/RunnerControls"; import { MOBILE_MEDIA_QUERY } from "../../../../../utils/mediaQueryBreakpoints"; import classNames from "classnames"; -const { createError } = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); +const { createError } = ApiCallHandler({ reactAppApiEndpoint: "TODORAAE" }); const externalLibraries = { "./pygal/__init__.js": { diff --git a/src/components/Login/LogoutButton.jsx b/src/components/Login/LogoutButton.jsx index 6dee6172b..932dd7ffc 100644 --- a/src/components/Login/LogoutButton.jsx +++ b/src/components/Login/LogoutButton.jsx @@ -6,7 +6,7 @@ import PropTypes from "prop-types"; const LogoutButton = ({ className, user }) => { const { t } = useTranslation(); - const userManager = UserManager({ reactAppAuthenticationUrl: "TODO" }); + const userManager = UserManager({ reactAppAuthenticationUrl: "TODORAAU" }); const onLogoutButtonClick = async (event) => { event.preventDefault(); diff --git a/src/components/Modals/NewProjectModal.jsx b/src/components/Modals/NewProjectModal.jsx index 27878ee29..78d20e1e2 100644 --- a/src/components/Modals/NewProjectModal.jsx +++ b/src/components/Modals/NewProjectModal.jsx @@ -12,7 +12,7 @@ import HTMLIcon from "../../assets/icons/html.svg"; import PythonIcon from "../../assets/icons/python.svg"; const { createOrUpdateProject } = ApiCallHandler({ - reactAppApiEndpoint: "TODO", + reactAppApiEndpoint: "TODORAAE", }); const NewProjectModal = () => { diff --git a/src/components/Modals/NewProjectModal.test.js b/src/components/Modals/NewProjectModal.test.js index 131a0e414..47f5e0151 100644 --- a/src/components/Modals/NewProjectModal.test.js +++ b/src/components/Modals/NewProjectModal.test.js @@ -11,7 +11,7 @@ import { } from "../../utils/defaultProjects"; const { createOrUpdateProject } = ApiCallHandler({ - reactAppApiEndpoint: "TODO", + reactAppApiEndpoint: "TODORAAE", }); const mockNavigate = jest.fn(); diff --git a/src/containers/WebComponentLoader.jsx b/src/containers/WebComponentLoader.jsx index 7964ec7b5..3fa4f3563 100644 --- a/src/containers/WebComponentLoader.jsx +++ b/src/containers/WebComponentLoader.jsx @@ -31,24 +31,25 @@ const WebComponentLoader = (props) => { const { assetsIdentifier, authKey, - identifier, code, - senseHatAlwaysEnabled = false, - instructions, - withProjectbar = false, - projectNameEditable = false, - withSidebar = false, - sidebarOptions = [], - theme, - outputPanels = ["text", "visual"], embedded = false, hostStyles, // Pass in styles from the host - showSavePrompt = false, + identifier, + instructions, loadRemixDisabled = false, - readOnly = false, outputOnly = false, + outputPanels = ["text", "visual"], outputSplitView = false, + projectNameEditable = false, + reactAppApiEndpoint, + readOnly = false, + senseHatAlwaysEnabled = false, + showSavePrompt = false, + sidebarOptions = [], + theme, useEditorStyles = false, // If true use the standard editor styling for the web component + withProjectbar = false, + withSidebar = false, } = props; const dispatch = useDispatch(); const { t } = useTranslation(); @@ -66,19 +67,19 @@ const WebComponentLoader = (props) => { const justLoaded = useSelector((state) => state.editor.justLoaded); const remixLoadFailed = useSelector((state) => state.editor.remixLoadFailed); const hasShownSavePrompt = useSelector( - (state) => state.editor.hasShownSavePrompt, + (state) => state.editor.hasShownSavePrompt ); const saveTriggered = useSelector((state) => state.editor.saveTriggered); const modals = useSelector((state) => state.editor.modals); const errorModalShowing = useSelector( - (state) => state.editor.errorModalShowing, + (state) => state.editor.errorModalShowing ); const newFileModalShowing = useSelector( - (state) => state.editor.newFileModalShowing, + (state) => state.editor.newFileModalShowing ); const renameFileModalShowing = useSelector( - (state) => state.editor.renameFileModalShowing, + (state) => state.editor.renameFileModalShowing ); const [cookies, setCookie] = useCookies(["theme", "fontSize"]); @@ -118,6 +119,7 @@ const WebComponentLoader = (props) => { }, [projectOwner, justLoaded]); useProject({ + reactAppApiEndpoint, projectIdentifier: projectIdentifier, assetsIdentifier: assetsIdentifier, code, diff --git a/src/hooks/useProject.js b/src/hooks/useProject.js index 20ba93fa5..0d85300b8 100644 --- a/src/hooks/useProject.js +++ b/src/hooks/useProject.js @@ -6,6 +6,7 @@ import { defaultPythonProject } from "../utils/defaultProjects"; import { useTranslation } from "react-i18next"; export const useProject = ({ + reactAppApiEndpoint = null, assetsIdentifier = null, projectIdentifier = null, code = null, @@ -25,7 +26,7 @@ export const useProject = ({ ? null : JSON.parse(localStorage.getItem(id || "project")); const [cachedProject, setCachedProject] = useState( - getCachedProject(projectIdentifier), + getCachedProject(projectIdentifier) ); const { i18n } = useTranslation(); const dispatch = useDispatch(); @@ -54,11 +55,12 @@ export const useProject = ({ if (assetsIdentifier) { dispatch( syncProject("load")({ + reactAppApiEndpoint, identifier: assetsIdentifier, locale: i18n.language, accessToken, assetsOnly: true, - }), + }) ); return; } @@ -66,10 +68,11 @@ export const useProject = ({ if (projectIdentifier) { dispatch( syncProject("load")({ + reactAppApiEndpoint, identifier: projectIdentifier, locale: i18n.language, accessToken: accessToken, - }), + }) ); return; } @@ -103,9 +106,10 @@ export const useProject = ({ if (!remixLoadFailed && !loadDispatched.current) { dispatch( syncProject("loadRemix")({ + reactAppApiEndpoint, identifier: projectIdentifier, accessToken: accessToken, - }), + }) ); // Prevents a failure on the initial render (using a ref to avoid triggering a render) @@ -119,10 +123,11 @@ export const useProject = ({ if (remixLoadFailed && !loadDispatched.current) { dispatch( syncProject("load")({ + reactAppApiEndpoint, identifier: projectIdentifier, locale: i18n.language, accessToken: accessToken, - }), + }) ); loadDispatched.current = true; @@ -137,7 +142,7 @@ export const useProject = ({ const mainComponent = project.components?.find( (component) => component.name === defaultName && - component.extension === defaultExtension, + component.extension === defaultExtension ) || { name: defaultName, extension: defaultExtension, content: "" }; const otherComponents = @@ -146,7 +151,7 @@ export const useProject = ({ !( component.name === defaultName && component.extension === defaultExtension - ), + ) ) || []; const updatedProject = { diff --git a/src/hooks/useProjectPersistence.js b/src/hooks/useProjectPersistence.js index 32d36ed51..3a2b0bdf7 100644 --- a/src/hooks/useProjectPersistence.js +++ b/src/hooks/useProjectPersistence.js @@ -17,6 +17,7 @@ export const useProjectPersistence = ({ saveTriggered, }) => { const dispatch = useDispatch(); + dispatch(showLoginToSaveModal()); const saveToLocalStorage = (project) => { localStorage.setItem( diff --git a/src/redux/EditorSlice.js b/src/redux/EditorSlice.js index 9a9a5387c..99d773ac5 100644 --- a/src/redux/EditorSlice.js +++ b/src/redux/EditorSlice.js @@ -7,23 +7,29 @@ import { } from "./reducers/loadProjectReducers"; import ApiCallHandler from "../utils/apiCallHandler"; -const { - createOrUpdateProject, - readProject, - loadRemix, - createRemix, - deleteProject, - readProjectList, - loadAssets, -} = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); - export const syncProject = (actionName) => createAsyncThunk( `editor/${actionName}Project`, async ( - { project, identifier, locale, accessToken, autosave, assetsOnly }, - { rejectWithValue }, + { + reactAppApiEndpoint, + project, + identifier, + locale, + accessToken, + autosave, + assetsOnly, + }, + { rejectWithValue } ) => { + const { + createOrUpdateProject, + readProject, + loadRemix, + createRemix, + deleteProject, + loadAssets, + } = ApiCallHandler({ reactAppApiEndpoint }); let response; switch (actionName) { case "load": @@ -68,19 +74,22 @@ export const syncProject = (actionName) => return false; } }, - }, + } ); export const loadProjectList = createAsyncThunk( `editor/loadProjectList`, - async ({ page, accessToken }) => { + async ({ reactAppApiEndpoint, page, accessToken }) => { + const { readProjectList } = ApiCallHandler({ + reactAppApiEndpoint, + }); const response = await readProjectList(page, accessToken); return { projects: response.data, page, links: parseLinkHeader(response.headers.link), }; - }, + } ); const initialState = { @@ -144,10 +153,10 @@ export const EditorSlice = createSlice({ .map((fileNames) => fileNames.includes(action.payload)) .indexOf(true); const closedFileIndex = state.openFiles[panelIndex].indexOf( - action.payload, + action.payload ); state.openFiles[panelIndex] = state.openFiles[panelIndex].filter( - (fileName) => fileName !== action.payload, + (fileName) => fileName !== action.payload ); if ( state.focussedFileIndices[panelIndex] >= diff --git a/src/redux/EditorSlice.test.js b/src/redux/EditorSlice.test.js index 1b93feb5a..a97e56ac6 100644 --- a/src/redux/EditorSlice.test.js +++ b/src/redux/EditorSlice.test.js @@ -16,6 +16,8 @@ import reducer, { setReadOnly, } from "./EditorSlice"; +jest.mock("../utils/apiCallHandler"); + const { createOrUpdateProject, createRemix, @@ -23,9 +25,7 @@ const { loadAssets, readProject, readProjectList, -} = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); - -jest.mock("../utils/apiCallHandler"); +} = ApiCallHandler({ reactAppApiEndpoint: "" }); test("Action stopCodeRun sets codeRunStopped to true", () => { const previousState = { diff --git a/src/redux/reducers/loadProjectReducers.test.js b/src/redux/reducers/loadProjectReducers.test.js index 177ef374e..1c1b6792e 100644 --- a/src/redux/reducers/loadProjectReducers.test.js +++ b/src/redux/reducers/loadProjectReducers.test.js @@ -6,7 +6,7 @@ import { loadProjectRejected } from "./loadProjectReducers"; jest.mock("../../utils/apiCallHandler"); -const { readProject } = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); +const { readProject } = ApiCallHandler({ reactAppApiEndpoint: "TODORAAE" }); const requestingAProject = function (project, projectFile) { const dispatch = jest.fn(); const initialState = { diff --git a/src/utils/apiCallHandler.test.js b/src/utils/apiCallHandler.test.js index 6ebc33d70..38e1f0d27 100644 --- a/src/utils/apiCallHandler.test.js +++ b/src/utils/apiCallHandler.test.js @@ -11,7 +11,7 @@ const { uploadImages, readProjectList, createError, -} = ApiCallHandler({ reactAppApiEndpoint: "TODO" }); +} = ApiCallHandler({ reactAppApiEndpoint: "TODORAAE" }); jest.mock("axios"); const host = process.env.REACT_APP_API_ENDPOINT; diff --git a/src/utils/login.js b/src/utils/login.js index aabd23ef2..0601fed7d 100644 --- a/src/utils/login.js +++ b/src/utils/login.js @@ -1,5 +1,5 @@ import UserManager from "./userManager"; -const userManager = UserManager({ reactAppAuthenticationUrl: "TODO" }); +const userManager = UserManager({ reactAppAuthenticationUrl: "TODORAAU" }); export const login = ({ project, @@ -15,14 +15,14 @@ export const login = ({ if (accessDeniedData) { localStorage.setItem( "location", - `/projects/${accessDeniedData.identifier}`, + `/projects/${accessDeniedData.identifier}` ); } else { localStorage.setItem("location", loginRedirect || location.pathname); if (project) { localStorage.setItem( project.identifier || "project", - JSON.stringify(project), + JSON.stringify(project) ); } } diff --git a/src/utils/userManager.js b/src/utils/userManager.js index 210a56764..4fb896cc4 100644 --- a/src/utils/userManager.js +++ b/src/utils/userManager.js @@ -28,7 +28,7 @@ const UserManager = ({ reactAppAuthenticationUrl }) => { const mgr = createUserManager( userManagerConfig({ reactAppAuthenticationUrl, - }), + }) ); mgr.events.addAccessTokenExpired(() => { diff --git a/src/web-component.js b/src/web-component.js index 8ad70f5f1..a77fbaecb 100644 --- a/src/web-component.js +++ b/src/web-component.js @@ -46,26 +46,27 @@ class WebComponent extends HTMLElement { static get observedAttributes() { return [ - "host_styles", "assets_identifier", "auth_key", - "identifier", "code", - "sense_hat_always_enabled", + "embedded", + "host_styles", + "identifier", "instructions", - "with_projectbar", - "project_name_editable", - "with_sidebar", - "read_only", + "load_remix_disabled", "output_only", "output_panels", + "output_split_view", + "project_name_editable", + "react_app_api_endpoint", + "read_only", + "sense_hat_always_enabled", + "show_save_prompt", "sidebar_options", "theme", - "embedded", - "show_save_prompt", - "load_remix_disabled", - "output_split_view", "use_editor_styles", + "with_projectbar", + "with_sidebar", ]; } @@ -74,17 +75,17 @@ class WebComponent extends HTMLElement { if ( [ - "sense_hat_always_enabled", - "with_sidebar", - "with_projectbar", - "project_name_editable", - "show_save_prompt", + "embedded", "load_remix_disabled", "output_only", - "embedded", "output_split_view", - "use_editor_styles", + "project_name_editable", "read_only", + "sense_hat_always_enabled", + "show_save_prompt", + "use_editor_styles", + "with_projectbar", + "with_sidebar", ].includes(name) ) { value = newVal !== "false"; @@ -172,7 +173,7 @@ class WebComponent extends HTMLElement { - , + ); } } From 3a5ebb2365520ec067d1c8dcc0df9ba1d8760ff1 Mon Sep 17 00:00:00 2001 From: Conor Date: Tue, 29 Oct 2024 12:14:37 +0000 Subject: [PATCH 04/22] tidy up unused files, use redux for endpoint --- .env.example | 2 - src/app/store.js | 4 +- .../DownloadButton/DownloadButton.jsx | 10 +- .../DownloadButton/DownloadButton.test.js | 25 -- .../PyodideRunner/PyodideRunner.jsx | 14 +- .../Runners/PythonRunner/PythonRunner.jsx | 10 +- .../SkulptRunner/SkulptRunner.jsx | 25 +- src/components/Login/LoginButton.jsx | 42 ---- src/components/Login/LoginButton.test.js | 229 ------------------ src/components/Login/LoginMenu.jsx | 45 ---- src/components/Login/LoginMenu.test.js | 94 ------- src/components/Login/LogoutButton.jsx | 36 --- src/components/Login/LogoutButton.test.js | 45 ---- .../ProjectActionsMenu/ProjectActionsMenu.jsx | 51 ---- .../ProjectActionsMenu.test.js | 55 ----- .../Modals/AccessDeniedNoAuthModal.jsx | 76 ------ .../Modals/AccessDeniedNoAuthModal.test.js | 54 ----- .../AccessDeniedNoAuthModalEmbedded.jsx | 31 --- src/components/Modals/DeleteProjectModal.jsx | 72 ------ .../Modals/DeleteProjectModal.test.js | 94 ------- src/components/Modals/LoginToSaveModal.jsx | 62 ----- .../Modals/LoginToSaveModal.test.js | 45 ---- src/components/Modals/NewProjectModal.jsx | 98 -------- src/components/Modals/NewProjectModal.test.js | 120 --------- src/components/ProjectIndex/ProjectIndex.jsx | 110 --------- .../ProjectIndex/ProjectIndex.test.js | 130 ---------- .../ProjectIndex/ProjectIndexPagination.jsx | 53 ---- .../ProjectIndexPagination.test.js | 166 ------------- .../ProjectIndexHeader/ProjectIndexHeader.jsx | 23 -- .../ProjectListItem/ProjectListItem.jsx | 94 ------- .../ProjectListItem/ProjectListItem.test.js | 49 ---- .../ProjectListTable/ProjectListTable.jsx | 45 ---- .../ProjectListTable/ProjectListTable.test.js | 64 ----- src/containers/WebComponentLoader.jsx | 5 + src/hooks/useProjectPersistence.js | 4 - src/hooks/useProjectPersistence.test.js | 6 - src/redux/EditorSlice.js | 66 +---- src/redux/EditorSlice.test.js | 83 ------- src/redux/reducers/loadProjectReducers.js | 2 - .../reducers/loadProjectReducers.test.js | 1 - src/utils/i18n.js | 41 ---- src/utils/login.js | 33 --- 42 files changed, 39 insertions(+), 2275 deletions(-) delete mode 100644 src/components/Login/LoginButton.jsx delete mode 100644 src/components/Login/LoginButton.test.js delete mode 100644 src/components/Login/LoginMenu.jsx delete mode 100644 src/components/Login/LoginMenu.test.js delete mode 100644 src/components/Login/LogoutButton.jsx delete mode 100644 src/components/Login/LogoutButton.test.js delete mode 100644 src/components/Menus/ProjectActionsMenu/ProjectActionsMenu.jsx delete mode 100644 src/components/Menus/ProjectActionsMenu/ProjectActionsMenu.test.js delete mode 100644 src/components/Modals/AccessDeniedNoAuthModal.jsx delete mode 100644 src/components/Modals/AccessDeniedNoAuthModal.test.js delete mode 100644 src/components/Modals/AccessDeniedNoAuthModalEmbedded.jsx delete mode 100644 src/components/Modals/DeleteProjectModal.jsx delete mode 100644 src/components/Modals/DeleteProjectModal.test.js delete mode 100644 src/components/Modals/LoginToSaveModal.jsx delete mode 100644 src/components/Modals/LoginToSaveModal.test.js delete mode 100644 src/components/Modals/NewProjectModal.jsx delete mode 100644 src/components/Modals/NewProjectModal.test.js delete mode 100644 src/components/ProjectIndex/ProjectIndex.jsx delete mode 100644 src/components/ProjectIndex/ProjectIndex.test.js delete mode 100644 src/components/ProjectIndex/ProjectIndexPagination.jsx delete mode 100644 src/components/ProjectIndex/ProjectIndexPagination.test.js delete mode 100644 src/components/ProjectIndexHeader/ProjectIndexHeader.jsx delete mode 100644 src/components/ProjectListItem/ProjectListItem.jsx delete mode 100644 src/components/ProjectListItem/ProjectListItem.test.js delete mode 100644 src/components/ProjectListTable/ProjectListTable.jsx delete mode 100644 src/components/ProjectListTable/ProjectListTable.test.js delete mode 100644 src/utils/login.js diff --git a/.env.example b/.env.example index 85d2e38ad..ff6c8520e 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,8 @@ REACT_APP_AUTHENTICATION_CLIENT_ID='editor-dev' -REACT_APP_AUTHENTICATION_URL='http://localhost:9001' REACT_APP_SENTRY_DSN='' REACT_APP_SENTRY_ENV='local' PUBLIC_URL='http://localhost:3011' ASSETS_URL='http://localhost:3011' -REACT_APP_API_ENDPOINT='http://localhost:3009' REACT_APP_GOOGLE_TAG_MANAGER_ID='' REACT_APP_PLAUSIBLE_DATA_DOMAIN='' REACT_APP_PLAUSIBLE_SOURCE='' diff --git a/src/app/store.js b/src/app/store.js index 325418a8a..ec4e800b2 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -4,7 +4,9 @@ import InstructionsReducer from "../redux/InstructionsSlice"; import { reducer, loadUser } from "redux-oidc"; import UserManager from "../utils/userManager"; -const userManager = UserManager({ reactAppAuthenticationUrl: "TODORAAU" }); +// TODO - not used but keeping this in preparation for using +// src/components/Editor/ImageUploadButton/ImageUploadButton.jsx +const userManager = UserManager({ reactAppAuthenticationUrl: "TODO" }); const store = configureStore({ reducer: { editor: EditorReducer, diff --git a/src/components/DownloadButton/DownloadButton.jsx b/src/components/DownloadButton/DownloadButton.jsx index 978e4d4e3..b6c81b5f5 100644 --- a/src/components/DownloadButton/DownloadButton.jsx +++ b/src/components/DownloadButton/DownloadButton.jsx @@ -4,11 +4,10 @@ import { toSnakeCase } from "js-convert-case"; import JSZip from "jszip"; import JSZipUtils from "jszip-utils"; import { useTranslation } from "react-i18next"; -import { useDispatch, useSelector } from "react-redux"; +import { useSelector } from "react-redux"; import PropTypes from "prop-types"; import DesignSystemButton from "../DesignSystemButton/DesignSystemButton"; -import { closeLoginToSaveModal } from "../../redux/EditorSlice"; const DownloadButton = (props) => { const { @@ -20,10 +19,6 @@ const DownloadButton = (props) => { } = props; const { t } = useTranslation(); const project = useSelector((state) => state.editor.project); - const loginToSaveModalShowing = useSelector( - (state) => state.editor.loginToSaveModalShowing - ); - const dispatch = useDispatch(); const urlToPromise = (url) => { return new Promise(function (resolve, reject) { @@ -42,9 +37,6 @@ const DownloadButton = (props) => { window.plausible("Download"); } - if (loginToSaveModalShowing) { - dispatch(closeLoginToSaveModal()); - } const zip = new JSZip(); project.components.forEach((file) => { diff --git a/src/components/DownloadButton/DownloadButton.test.js b/src/components/DownloadButton/DownloadButton.test.js index 7c2c50d35..ef9e38253 100644 --- a/src/components/DownloadButton/DownloadButton.test.js +++ b/src/components/DownloadButton/DownloadButton.test.js @@ -6,7 +6,6 @@ import DownloadButton from "./DownloadButton"; import FileSaver from "file-saver"; import JSZip from "jszip"; import JSZipUtils from "jszip-utils"; -import { closeLoginToSaveModal } from "../../redux/EditorSlice"; jest.mock("file-saver"); jest.mock("jszip"); @@ -124,27 +123,3 @@ describe("Downloading project with no name set", () => { ); }); }); - -test("If login to save modal open, closes it when download clicked", () => { - JSZip.mockClear(); - const middlewares = []; - const mockStore = configureStore(middlewares); - const initialState = { - editor: { - project: { - components: [], - image_list: [], - }, - loginToSaveModalShowing: true, - }, - }; - const store = mockStore(initialState); - render( - - {}} /> - , - ); - const downloadButton = screen.queryByText("Download").parentElement; - fireEvent.click(downloadButton); - expect(store.getActions()).toEqual([closeLoginToSaveModal()]); -}); diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index 0eb0d5d4e..6d96e4b74 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -18,8 +18,6 @@ import OutputViewToggle from "../OutputViewToggle"; import { SettingsContext } from "../../../../../utils/settings"; import RunnerControls from "../../../../RunButton/RunnerControls"; -const { createError } = ApiCallHandler({ reactAppApiEndpoint: "TODORAAE" }); - const getWorkerURL = (url) => { const content = ` /* global PyodideWorker */ @@ -49,6 +47,7 @@ const PyodideRunner = (props) => { const userId = user?.profile?.user; const isSplitView = useSelector((s) => s.editor.isSplitView); const isEmbedded = useSelector((s) => s.editor.isEmbedded); + const reactAppApiEndpoint = useSelector((s) => s.editor.reactAppApiEndpoint); const codeRunTriggered = useSelector((s) => s.editor.codeRunTriggered); const codeRunStopped = useSelector((s) => s.editor.codeRunStopped); const output = useRef(); @@ -85,7 +84,7 @@ const PyodideRunner = (props) => { data.line, data.mistake, data.type, - data.info, + data.info ); break; case "handleVisual": @@ -182,6 +181,9 @@ const PyodideRunner = (props) => { errorMessage += `:\n${mistake}`; } + const { createError } = ApiCallHandler({ + reactAppApiEndpoint, + }); createError(projectIdentifier, userId, { errorType: type, errorMessage }); } @@ -208,8 +210,8 @@ const PyodideRunner = (props) => { projectImages.map(({ filename, url }) => fetch(url) .then((response) => response.arrayBuffer()) - .then((buffer) => writeFile(filename, buffer)), - ), + .then((buffer) => writeFile(filename, buffer)) + ) ); for (const { name, extension, content } of projectCode) { @@ -218,7 +220,7 @@ const PyodideRunner = (props) => { // program is the content of the component with name main and extension py const program = projectCode.find( - (component) => component.name === "main" && component.extension === "py", + (component) => component.name === "main" && component.extension === "py" ).content; if (interruptBuffer.current) { diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index 01bcf7ca5..52a50a096 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -16,10 +16,10 @@ const SKULPT_ONLY_MODULES = [ const PythonRunner = () => { const project = useSelector((state) => state.editor.project); const codeRunTriggered = useSelector( - (state) => state.editor.codeRunTriggered, + (state) => state.editor.codeRunTriggered ); const senseHatAlwaysEnabled = useSelector( - (state) => state.editor.senseHatAlwaysEnabled, + (state) => state.editor.senseHatAlwaysEnabled ); const [usePyodide, setUsePyodide] = useState(true); const { t } = useTranslation(); @@ -32,7 +32,7 @@ const PythonRunner = () => { const getImports = (code) => { const codeWithoutMultilineStrings = code.replace( /'''[\s\S]*?'''|"""[\s\S]*?"""/gm, - "", + "" ); const importRegex = /(?<=^\s*)(from\s+([a-zA-Z0-9_.]+)(\s+import\s+([a-zA-Z0-9_.]+))?)|(?<=^\s*)(import\s+([a-zA-Z0-9_.]+))/gm; @@ -43,7 +43,7 @@ const PythonRunner = () => { match .split(/from|import/) .filter(Boolean) - .map((s) => s.trim())[0], + .map((s) => s.trim())[0] ) : []; if (code.includes(`# ${t("input.comment.py5")}`)) { @@ -57,7 +57,7 @@ const PythonRunner = () => { try { const imports = getImports(component.content); const hasSkulptOnlyModules = imports.some((name) => - SKULPT_ONLY_MODULES.includes(name), + SKULPT_ONLY_MODULES.includes(name) ); if (hasSkulptOnlyModules || senseHatAlwaysEnabled) { setUsePyodide(false); diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx index 32ac8a5f2..f4ad01b31 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx @@ -24,8 +24,6 @@ import RunnerControls from "../../../../RunButton/RunnerControls"; import { MOBILE_MEDIA_QUERY } from "../../../../../utils/mediaQueryBreakpoints"; import classNames from "classnames"; -const { createError } = ApiCallHandler({ reactAppApiEndpoint: "TODORAAE" }); - const externalLibraries = { "./pygal/__init__.js": { path: `${process.env.ASSETS_URL}/shims/pygal/pygal.js`, @@ -59,20 +57,21 @@ const externalLibraries = { const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { const projectCode = useSelector((state) => state.editor.project.components); const mainComponent = projectCode?.find( - (component) => component.name === "main" && component.extension === "py", + (component) => component.name === "main" && component.extension === "py" ); const projectIdentifier = useSelector( - (state) => state.editor.project.identifier, + (state) => state.editor.project.identifier ); const user = useSelector((state) => state.auth.user); const isSplitView = useSelector((state) => state.editor.isSplitView); const isEmbedded = useSelector((state) => state.editor.isEmbedded); const isOutputOnly = useSelector((state) => state.editor.isOutputOnly); const codeRunTriggered = useSelector( - (state) => state.editor.codeRunTriggered, + (state) => state.editor.codeRunTriggered ); const codeRunStopped = useSelector((state) => state.editor.codeRunStopped); const drawTriggered = useSelector((state) => state.editor.drawTriggered); + const reactAppApiEndpoint = useSelector((s) => s.editor.reactAppApiEndpoint); const output = useRef(); const dispatch = useDispatch(); const { t } = useTranslation(); @@ -191,7 +190,7 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { .then((code) => { if (!code) { throw new Sk.builtin.ImportError( - "Failed to load remote module", + "Failed to load remote module" ); } externalLibraries[library].code = code; @@ -200,7 +199,7 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { function mapUrlToPromise(path) { // If the script is already in the DOM don't add it again. const existingScriptElement = document.querySelector( - `script[src="${path}"]`, + `script[src="${path}"]` ); if (!existingScriptElement) { return new Promise(function (resolve, _reject) { @@ -222,11 +221,11 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { (p, url) => { return p.then(() => mapUrlToPromise(url)); }, - Promise.resolve(), + Promise.resolve() ); // initial } else { promise = Promise.all( - (externalLibraryInfo.dependencies || []).map(mapUrlToPromise), + (externalLibraryInfo.dependencies || []).map(mapUrlToPromise) ); } @@ -236,10 +235,10 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { }) .catch(function () { throw new Sk.builtin.ImportError( - "Failed to load dependencies required", + "Failed to load dependencies required" ); }); - }), + }) ) ); } @@ -316,6 +315,8 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { userId = user.profile?.user; } + const { createError } = ApiCallHandler({ reactAppApiEndpoint }); + errorMessage = `${errorType}: ${errorDescription} on line ${lineNumber} of ${fileName}${ explanation ? `. ${explanation}` : "" }`; @@ -357,7 +358,7 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { if (prog.includes(`# ${t("input.comment.py5")}`)) { prog = prog.replace( `# ${t("input.comment.py5")}`, - "from py5_imported_mode import *", + "from py5_imported_mode import *" ); if (!prog.match(/(\nrun_sketch)/)) { diff --git a/src/components/Login/LoginButton.jsx b/src/components/Login/LoginButton.jsx deleted file mode 100644 index c4a703e8b..000000000 --- a/src/components/Login/LoginButton.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; -import { useLocation } from "react-router-dom"; -import { useSelector } from "react-redux"; -import Button from "../Button/Button"; -import { login } from "../../utils/login"; -import PropTypes from "prop-types"; - -const LoginButton = ({ buttonText, className, triggerSave, loginRedirect }) => { - const location = useLocation(); - const project = useSelector((state) => state.editor.project); - const accessDeniedData = useSelector( - (state) => state.editor.modals?.accessDenied || null, - ); - - const onLoginButtonClick = (event) => { - event.preventDefault(); - login({ - project, - location, - triggerSave, - accessDeniedData, - loginRedirect, - }); - }; - - return ( -