diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2f2199028..de3b9fe93 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -125,9 +125,10 @@ jobs: - name: Build WC bundle run: | - if [[ "${{ inputs.environment }}" != "production" ]]; then - yarn build:dev - fi + # TODO: Reinitialise when storybook build is fixed + # if [[ "${{ inputs.environment }}" != "production" ]]; then + # yarn build:dev + # fi yarn build env: PUBLIC_URL: ${{ needs.setup-environment.outputs.public_url }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c293f456..d1166906a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Fixed - Bug causing py-enigma code to disable stop button +- Crashing caused by excessive file sizes (#1138) ### Changed diff --git a/cypress/e2e/spec-wc-skulpt.cy.js b/cypress/e2e/spec-wc-skulpt.cy.js index 6f5e05b9f..5b733ca8a 100644 --- a/cypress/e2e/spec-wc-skulpt.cy.js +++ b/cypress/e2e/spec-wc-skulpt.cy.js @@ -121,25 +121,31 @@ describe("Running the code with skulpt", () => { runCode("import sense_hat"); cy.get("editor-wc") .shadow() - .find("#root") + .find(".skulptrunner") .should("contain", "Visual output"); }); it("does not render astro pi component on page load", () => { - cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw"); + cy.get("editor-wc") + .shadow() + .find(".skulptrunner") + .should("not.contain", "yaw"); }); it("renders astro pi component if sense hat imported", () => { runCode("import sense_hat"); cy.get("editor-wc").shadow().contains("Visual output").click(); - cy.get("editor-wc").shadow().find("#root").should("contain", "yaw"); + cy.get("editor-wc").shadow().find(".skulptrunner").should("contain", "yaw"); }); it("does not render astro pi component if sense hat unimported", () => { runCode("import sense_hat"); runCode("import p5"); cy.get("editor-wc").shadow().contains("Visual output").click(); - cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw"); + cy.get("editor-wc") + .shadow() + .find(".skulptrunner") + .should("not.contain", "yaw"); }); it("runs a simple turtle program", () => { diff --git a/package.json b/package.json index c10bbb230..762751131 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "@hello-pangea/dnd": "^16.2.0", "@juggle/resize-observer": "^3.3.1", "@lezer/highlight": "^1.0.0", - "@raspberrypifoundation/design-system-core": "^0.1.9", - "@raspberrypifoundation/design-system-react": "^0.1.5", + "@raspberrypifoundation/design-system-core": "^1.6.0", + "@raspberrypifoundation/design-system-react": "^1.6.0", "@react-three/drei": "9.114.3", "@react-three/fiber": "^8.0.13", "@reduxjs/toolkit": "^1.6.2", @@ -45,6 +45,7 @@ "js-convert-case": "^4.2.0", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", + "material-symbols": "^0.27.0", "mime-types": "^2.1.35", "node-html-parser": "^6.1.5", "oidc-client": "^1.11.5", diff --git a/src/assets/stylesheets/EditorPanel.scss b/src/assets/stylesheets/EditorPanel.scss index d7452bbc3..5b52b3747 100644 --- a/src/assets/stylesheets/EditorPanel.scss +++ b/src/assets/stylesheets/EditorPanel.scss @@ -39,3 +39,7 @@ @include font-size-2(regular); } } + +.rpf-alert { + margin: 0; +} diff --git a/src/assets/stylesheets/ExternalStyles.scss b/src/assets/stylesheets/ExternalStyles.scss index b9f63b27e..83acc15eb 100644 --- a/src/assets/stylesheets/ExternalStyles.scss +++ b/src/assets/stylesheets/ExternalStyles.scss @@ -2,3 +2,5 @@ @use "../../../node_modules/react-toggle/style.css"; @use "../../../node_modules/prismjs/plugins/line-numbers/prism-line-numbers.css"; @use "../../../node_modules/prismjs/plugins/line-highlight/prism-line-highlight.css"; +@use "../../../node_modules/@raspberrypifoundation/design-system-core/scss/components/alert.scss"; +@use "../../../node_modules/material-symbols/sharp.scss"; diff --git a/src/assets/stylesheets/Tabs.scss b/src/assets/stylesheets/Tabs.scss index 2a6c42bd8..beb3856e3 100644 --- a/src/assets/stylesheets/Tabs.scss +++ b/src/assets/stylesheets/Tabs.scss @@ -72,6 +72,7 @@ padding-inline-end: 0; } } + &__tab-close-btn { block-size: 100%; padding: $space-0-25; @@ -112,6 +113,7 @@ &__tab-panel--selected { flex: 1; display: flex; + flex-direction: column; overflow-y: auto; } } diff --git a/src/components/Editor/EditorPanel/EditorPanel.jsx b/src/components/Editor/EditorPanel/EditorPanel.jsx index 29b148e56..951cfb70b 100644 --- a/src/components/Editor/EditorPanel/EditorPanel.jsx +++ b/src/components/Editor/EditorPanel/EditorPanel.jsx @@ -1,6 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import "../../../assets/stylesheets/EditorPanel.scss"; -import React, { useRef, useEffect, useContext } from "react"; +import React, { useRef, useEffect, useContext, useState } from "react"; import { useSelector, useDispatch } from "react-redux"; import { updateProjectComponent } from "../../../redux/EditorSlice"; import { useCookies } from "react-cookie"; @@ -11,16 +11,20 @@ import { EditorState } from "@codemirror/state"; import { defaultKeymap, indentWithTab } from "@codemirror/commands"; import { indentationMarkers } from "@replit/codemirror-indentation-markers"; import { indentUnit } from "@codemirror/language"; +import "material-symbols"; import { html } from "@codemirror/lang-html"; import { css } from "@codemirror/lang-css"; import { python } from "@codemirror/lang-python"; import { javascript } from "@codemirror/lang-javascript"; +import { Alert } from "@raspberrypifoundation/design-system-react"; import { editorLightTheme } from "../../../assets/themes/editorLightTheme"; import { editorDarkTheme } from "../../../assets/themes/editorDarkTheme"; import { SettingsContext } from "../../../utils/settings"; +const MAX_CHARACTERS = 8500000; + const EditorPanel = ({ extension = "html", fileName = "index" }) => { const editor = useRef(); const project = useSelector((state) => state.editor.project); @@ -29,6 +33,7 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { const dispatch = useDispatch(); const { t } = useTranslation(); const settings = useContext(SettingsContext); + const [characterLimitExceeded, setCharacterLimitExceeded] = useState(false); const updateStoredProject = (content) => { dispatch( @@ -86,6 +91,16 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { customIndentUnit = " "; } + const limitCharacters = EditorState.transactionFilter.of((transaction) => { + const newDoc = transaction.newDoc; + if (newDoc.length > MAX_CHARACTERS) { + setCharacterLimitExceeded(true); + return []; + } + setCharacterLimitExceeded(false); + return transaction; + }); + const startState = EditorState.create({ doc: code, extensions: [ @@ -98,6 +113,7 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { indentationMarkers(), indentUnit.of(customIndentUnit), EditorView.editable.of(!readOnly), + limitCharacters, ], }); @@ -123,7 +139,18 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { }, [cookies]); return ( -
+ <> +
+ {characterLimitExceeded && ( + + )} + ); }; diff --git a/src/components/Editor/EditorPanel/EditorPanel.test.js b/src/components/Editor/EditorPanel/EditorPanel.test.js index 872a2916a..5c5c9783a 100644 --- a/src/components/Editor/EditorPanel/EditorPanel.test.js +++ b/src/components/Editor/EditorPanel/EditorPanel.test.js @@ -1,7 +1,7 @@ import configureStore from "redux-mock-store"; import { Provider } from "react-redux"; import { SettingsContext } from "../../../utils/settings"; -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; import { axe, toHaveNoViolations } from "jest-axe"; import EditorPanel from "./EditorPanel"; @@ -88,3 +88,48 @@ describe("When read only", () => { expect(editorInputArea).toHaveAttribute("contenteditable", "false"); }); }); + +describe("When excessive file content is pasted into the editor", () => { + beforeEach(() => { + renderEditorPanel({ readOnly: false }); + const editorInputArea = screen.getByLabelText("editorPanel.ariaLabel"); + const massiveFileContent = "mango".repeat(2000000); + fireEvent.paste(editorInputArea, { + clipboardData: { + getData: () => massiveFileContent, + }, + }); + }); + + test("It does not display the file content", () => { + expect(screen.queryByText(/mango/)).not.toBeInTheDocument(); + }); + + test("Character limit exceeded message is displayed", () => { + expect( + screen.getByText("editorPanel.characterLimitError"), + ).toBeInTheDocument(); + }); + + test("It allows the user to input text below the limit", () => { + const editorInputArea = screen.getByLabelText("editorPanel.ariaLabel"); + fireEvent.paste(editorInputArea, { + clipboardData: { + getData: () => "mango", + }, + }); + expect(screen.getByText("mango")).toBeInTheDocument(); + }); + + test("It removes the character limit exceeded message when the user inputs text below the limit", () => { + const editorInputArea = screen.getByLabelText("editorPanel.ariaLabel"); + fireEvent.paste(editorInputArea, { + clipboardData: { + getData: () => "mango", + }, + }); + expect( + screen.queryByText("editorPanel.characterLimitError"), + ).not.toBeInTheDocument(); + }); +}); diff --git a/src/hooks/useProjectPersistence.js b/src/hooks/useProjectPersistence.js index 749f9ec28..5c8bc03f6 100644 --- a/src/hooks/useProjectPersistence.js +++ b/src/hooks/useProjectPersistence.js @@ -8,6 +8,8 @@ import { } from "../redux/EditorSlice"; import { showLoginPrompt, showSavePrompt } from "../utils/Notifications"; +const COMBINED_FILE_SIZE_SOFT_LIMIT = 1000000; + export const useProjectPersistence = ({ user, project = {}, @@ -18,6 +20,13 @@ export const useProjectPersistence = ({ }) => { const dispatch = useDispatch(); + const combinedFileSize = project.components?.reduce( + (sum, component) => sum + component.content.length, + 0, + ); + const autoSaveInterval = + combinedFileSize > COMBINED_FILE_SIZE_SOFT_LIMIT ? 10000 : 2000; + const saveToLocalStorage = (project) => { localStorage.setItem( project.identifier || "project", @@ -90,7 +99,7 @@ export const useProjectPersistence = ({ } } } - }, 2000); + }, autoSaveInterval); return () => clearTimeout(debouncer); }, [dispatch, project, user, hasShownSavePrompt]); // eslint-disable-line react-hooks/exhaustive-deps diff --git a/src/hooks/useProjectPersistence.test.js b/src/hooks/useProjectPersistence.test.js index b1b69efac..0ccf4fc91 100644 --- a/src/hooks/useProjectPersistence.test.js +++ b/src/hooks/useProjectPersistence.test.js @@ -314,6 +314,34 @@ describe("When logged in", () => { }); }); + test("Increases save interval for large projects", async () => { + const largeProject = { + ...project, + components: [ + { + name: "main", + extension: "py", + content: "mango".repeat(200001), + }, + ], + }; + renderHook(() => + useProjectPersistence({ + user: user1, + project: largeProject, + saveTriggered: false, + }), + ); + jest.advanceTimersByTime(2500); + expect(saveProject).not.toHaveBeenCalled(); + jest.runAllTimers(); + expect(saveProject).toHaveBeenCalledWith({ + project: largeProject, + accessToken: user1.access_token, + autosave: true, + }); + }); + test("Saves project to database if save triggered", async () => { renderHook(() => useProjectPersistence({ diff --git a/src/utils/i18n.js b/src/utils/i18n.js index c044d0f05..b61ab4d92 100644 --- a/src/utils/i18n.js +++ b/src/utils/i18n.js @@ -123,6 +123,9 @@ i18n }, editorPanel: { ariaLabel: "editor text input", + characterLimitError: "Error: Character limit reached", + characterLimitExplanation: + "Files in the editor are limited to {{maxCharacters}} characters", viewOnly: "View only", }, filePanel: { diff --git a/storybook/package.json b/storybook/package.json index c5c064b00..3045f9826 100644 --- a/storybook/package.json +++ b/storybook/package.json @@ -7,6 +7,8 @@ "build-storybook": "build-storybook" }, "dependencies": { + "@raspberrypifoundation/design-system-core": "^1.6.0", + "@raspberrypifoundation/design-system-react": "^1.6.0", "@reduxjs/toolkit": "^1.8.3", "@storybook/addon-actions": "6.5.10", "@storybook/addon-essentials": "6.5.10", diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 8c82e4e70..2fe3ab6b2 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -2835,6 +2835,31 @@ __metadata: languageName: node linkType: hard +"@raspberrypifoundation/design-system-core@npm:^1.6.0": + version: 1.6.1 + resolution: "@raspberrypifoundation/design-system-core@npm:1.6.1" + dependencies: + classnames: ^2.3.2 + checksum: a204d21a373e89b08a2cda763591f7be22c5d72c2974f1cd6f4331fe5c29f3816979e0a1224e7f15cfd03190f28e171329e5e729e2ffec8ea00ca8910406a1a9 + languageName: node + linkType: hard + +"@raspberrypifoundation/design-system-react@npm:^1.6.0": + version: 1.6.0 + resolution: "@raspberrypifoundation/design-system-react@npm:1.6.0" + dependencies: + classnames: ^2.3.2 + material-symbols: ^0.14.5 + prop-types: ^15.8.1 + react: ^18.2.0 + react-dom: ^18.2.0 + react-router-dom: ^6.24.0 + peerDependencies: + react-router-dom: ^6.24.0 + checksum: 6bb6341f90d3c4fa569817813de7a1a9e69a23c12ca2cf0f20d47b9d0954771e237bda27eedba93b91a0d1d81fca32e155415ddc902947630711b38583f97ac5 + languageName: node + linkType: hard + "@reduxjs/toolkit@npm:^1.8.3": version: 1.9.5 resolution: "@reduxjs/toolkit@npm:1.9.5" @@ -2862,6 +2887,13 @@ __metadata: languageName: node linkType: hard +"@remix-run/router@npm:1.21.0": + version: 1.21.0 + resolution: "@remix-run/router@npm:1.21.0" + checksum: d9477a7772053ad0ffcf03385cfb1a54e56f8a56d1f9f5062de3b1dfcbd019dd73282a00a5a72aa55c120771110982448c165c1405d64540aaef13051a8e45cc + languageName: node + linkType: hard + "@storybook/addon-actions@npm:6.5.10": version: 6.5.10 resolution: "@storybook/addon-actions@npm:6.5.10" @@ -6654,6 +6686,13 @@ __metadata: languageName: node linkType: hard +"classnames@npm:^2.3.2": + version: 2.5.1 + resolution: "classnames@npm:2.5.1" + checksum: da424a8a6f3a96a2e87d01a432ba19315503294ac7e025f9fece656db6b6a0f7b5003bb1fbb51cbb0d9624d964f1b9bb35a51c73af9b2434c7b292c42231c1e5 + languageName: node + linkType: hard + "clean-css@npm:^4.2.3": version: 4.2.4 resolution: "clean-css@npm:4.2.4" @@ -11007,6 +11046,13 @@ __metadata: languageName: node linkType: hard +"material-symbols@npm:^0.14.5": + version: 0.14.7 + resolution: "material-symbols@npm:0.14.7" + checksum: 191dea0145eadabd3122b891b965d8568793c3f53a540186a30b459948a8a0c00938642dcca3c4fcca483b08d7480d02722c6de10caeeeae30437306a79f64b6 + languageName: node + linkType: hard + "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" @@ -13204,7 +13250,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.0.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2": +"prop-types@npm:^15.0.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -13470,6 +13516,18 @@ __metadata: languageName: node linkType: hard +"react-dom@npm:^18.2.0": + version: 18.3.1 + resolution: "react-dom@npm:18.3.1" + dependencies: + loose-envify: ^1.1.0 + scheduler: ^0.23.2 + peerDependencies: + react: ^18.3.1 + checksum: 298954ecd8f78288dcaece05e88b570014d8f6dce5db6f66e6ee91448debeb59dcd31561dddb354eee47e6c1bb234669459060deb238ed0213497146e555a0b9 + languageName: node + linkType: hard + "react-element-to-jsx-string@npm:^14.3.4": version: 14.3.4 resolution: "react-element-to-jsx-string@npm:14.3.4" @@ -13591,6 +13649,19 @@ __metadata: languageName: node linkType: hard +"react-router-dom@npm:^6.24.0": + version: 6.28.0 + resolution: "react-router-dom@npm:6.28.0" + dependencies: + "@remix-run/router": 1.21.0 + react-router: 6.28.0 + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 0cf4658a92bc66f50ec9d8518c36aa5a402bcadce71fb624ed6f900d73a29ea87ff904a4f2c42279107e75e80cc08c6192563fadcc5d4e642e6d476e38e83b21 + languageName: node + linkType: hard + "react-router@npm:6.18.0": version: 6.18.0 resolution: "react-router@npm:6.18.0" @@ -13602,6 +13673,17 @@ __metadata: languageName: node linkType: hard +"react-router@npm:6.28.0": + version: 6.28.0 + resolution: "react-router@npm:6.28.0" + dependencies: + "@remix-run/router": 1.21.0 + peerDependencies: + react: ">=16.8" + checksum: 23246ca957b5c2bc8d6f9a81fee2df2ce4fc3feca3ec27c2fd85999568fc1299a4e8273e4ab70b6f3acd43a1fb45e0c93cb01ef77e68c9f9e1f7e4f42a1419ea + languageName: node + linkType: hard + "react-style-singleton@npm:^2.2.1": version: 2.2.1 resolution: "react-style-singleton@npm:2.2.1" @@ -13630,6 +13712,15 @@ __metadata: languageName: node linkType: hard +"react@npm:^18.2.0": + version: 18.3.1 + resolution: "react@npm:18.3.1" + dependencies: + loose-envify: ^1.1.0 + checksum: a27bcfa8ff7c15a1e50244ad0d0c1cb2ad4375eeffefd266a64889beea6f6b64c4966c9b37d14ee32d6c9fcd5aa6ba183b6988167ab4d127d13e7cb5b386a376 + languageName: node + linkType: hard + "read-pkg-up@npm:^1.0.1": version: 1.0.1 resolution: "read-pkg-up@npm:1.0.1" @@ -14246,6 +14337,15 @@ __metadata: languageName: node linkType: hard +"scheduler@npm:^0.23.2": + version: 0.23.2 + resolution: "scheduler@npm:0.23.2" + dependencies: + loose-envify: ^1.1.0 + checksum: 3e82d1f419e240ef6219d794ff29c7ee415fbdc19e038f680a10c067108e06284f1847450a210b29bbaf97b9d8a97ced5f624c31c681248ac84c80d56ad5a2c4 + languageName: node + linkType: hard + "schema-utils@npm:2.7.0": version: 2.7.0 resolution: "schema-utils@npm:2.7.0" @@ -14862,6 +14962,8 @@ __metadata: version: 0.0.0-use.local resolution: "storybook@workspace:." dependencies: + "@raspberrypifoundation/design-system-core": ^1.6.0 + "@raspberrypifoundation/design-system-react": ^1.6.0 "@reduxjs/toolkit": ^1.8.3 "@storybook/addon-actions": 6.5.10 "@storybook/addon-essentials": 6.5.10 diff --git a/webpack.config.js b/webpack.config.js index 4a0fc9f70..0666b78f8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -67,6 +67,14 @@ module.exports = { }, ], }, + { + test: /\.(woff|woff2|eot|ttf|otf)$/, + use: [ + { + loader: "url-loader", + }, + ], + }, ], }, resolve: { diff --git a/yarn.lock b/yarn.lock index eb1234465..ab941ab3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2697,24 +2697,28 @@ __metadata: languageName: node linkType: hard -"@raspberrypifoundation/design-system-core@npm:^0.1.9": - version: 0.1.16 - resolution: "@raspberrypifoundation/design-system-core@npm:0.1.16" +"@raspberrypifoundation/design-system-core@npm:^1.6.0": + version: 1.6.0 + resolution: "@raspberrypifoundation/design-system-core@npm:1.6.0" dependencies: classnames: ^2.3.2 - checksum: 015534a27d500ff56f7f425584676bae4a7bb818372f2a5b3c587c6e350a2d8949bc053268ecf03420c301a72157cb7b316c9a4dbf73fc525e797b9141c4f2a7 + checksum: 3eda17c4068e752daf070b6cbce9630b1b51997b911a4753b73f2c3726a40ec6bfba1b751380a4ad6f51e3e2e8e9cac66a7709343a7de9c5c8b074b409742a26 languageName: node linkType: hard -"@raspberrypifoundation/design-system-react@npm:^0.1.5": - version: 0.1.5 - resolution: "@raspberrypifoundation/design-system-react@npm:0.1.5" +"@raspberrypifoundation/design-system-react@npm:^1.6.0": + version: 1.6.0 + resolution: "@raspberrypifoundation/design-system-react@npm:1.6.0" dependencies: classnames: ^2.3.2 + material-symbols: ^0.14.5 prop-types: ^15.8.1 react: ^18.2.0 react-dom: ^18.2.0 - checksum: 7dc02f8d68a8dc5899dbb9e671eb062b0278fcdda195755d935e2fd4f1c692166790df94d13adf083d8b7ee8bf399bcb05077e301296cc1b4dc0763b44ab9651 + react-router-dom: ^6.24.0 + peerDependencies: + react-router-dom: ^6.24.0 + checksum: 6bb6341f90d3c4fa569817813de7a1a9e69a23c12ca2cf0f20d47b9d0954771e237bda27eedba93b91a0d1d81fca32e155415ddc902947630711b38583f97ac5 languageName: node linkType: hard @@ -2736,8 +2740,8 @@ __metadata: "@juggle/resize-observer": ^3.3.1 "@lezer/highlight": ^1.0.0 "@pmmmwh/react-refresh-webpack-plugin": 0.4.3 - "@raspberrypifoundation/design-system-core": ^0.1.9 - "@raspberrypifoundation/design-system-react": ^0.1.5 + "@raspberrypifoundation/design-system-core": ^1.6.0 + "@raspberrypifoundation/design-system-react": ^1.6.0 "@react-three/drei": 9.114.3 "@react-three/fiber": ^8.0.13 "@react-three/test-renderer": 8.2.1 @@ -2812,6 +2816,7 @@ __metadata: js-convert-case: ^4.2.0 jszip: ^3.10.1 jszip-utils: ^0.1.0 + material-symbols: ^0.27.0 mime-types: ^2.1.35 mini-css-extract-plugin: 0.11.3 mock-match-media: ^0.4.3 @@ -3071,6 +3076,13 @@ __metadata: languageName: node linkType: hard +"@remix-run/router@npm:1.21.0": + version: 1.21.0 + resolution: "@remix-run/router@npm:1.21.0" + checksum: d9477a7772053ad0ffcf03385cfb1a54e56f8a56d1f9f5062de3b1dfcbd019dd73282a00a5a72aa55c120771110982448c165c1405d64540aaef13051a8e45cc + languageName: node + linkType: hard + "@replit/codemirror-indentation-markers@npm:^6.1.0": version: 6.5.3 resolution: "@replit/codemirror-indentation-markers@npm:6.5.3" @@ -12498,6 +12510,20 @@ __metadata: languageName: node linkType: hard +"material-symbols@npm:^0.14.5": + version: 0.14.7 + resolution: "material-symbols@npm:0.14.7" + checksum: 191dea0145eadabd3122b891b965d8568793c3f53a540186a30b459948a8a0c00938642dcca3c4fcca483b08d7480d02722c6de10caeeeae30437306a79f64b6 + languageName: node + linkType: hard + +"material-symbols@npm:^0.27.0": + version: 0.27.0 + resolution: "material-symbols@npm:0.27.0" + checksum: 6a585863e75c291aab6104e4ebb38e344dd0e3d39e14653bcb529d558f2b55a3f00d2fc58e59b2ee2d08dc0b7e209fb7ac88ed49cf7158c7fbaf9697edd514ff + languageName: node + linkType: hard + "mathml-tag-names@npm:^2.1.3": version: 2.1.3 resolution: "mathml-tag-names@npm:2.1.3" @@ -15342,6 +15368,19 @@ __metadata: languageName: node linkType: hard +"react-router-dom@npm:^6.24.0": + version: 6.28.0 + resolution: "react-router-dom@npm:6.28.0" + dependencies: + "@remix-run/router": 1.21.0 + react-router: 6.28.0 + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 0cf4658a92bc66f50ec9d8518c36aa5a402bcadce71fb624ed6f900d73a29ea87ff904a4f2c42279107e75e80cc08c6192563fadcc5d4e642e6d476e38e83b21 + languageName: node + linkType: hard + "react-router-dom@npm:^6.7.0": version: 6.27.0 resolution: "react-router-dom@npm:6.27.0" @@ -15366,6 +15405,17 @@ __metadata: languageName: node linkType: hard +"react-router@npm:6.28.0": + version: 6.28.0 + resolution: "react-router@npm:6.28.0" + dependencies: + "@remix-run/router": 1.21.0 + peerDependencies: + react: ">=16.8" + checksum: 23246ca957b5c2bc8d6f9a81fee2df2ce4fc3feca3ec27c2fd85999568fc1299a4e8273e4ab70b6f3acd43a1fb45e0c93cb01ef77e68c9f9e1f7e4f42a1419ea + languageName: node + linkType: hard + "react-shallow-renderer@npm:^16.13.1": version: 16.15.0 resolution: "react-shallow-renderer@npm:16.15.0"