From 5b990d861bfc86d0312556c611ecba41843343af Mon Sep 17 00:00:00 2001 From: yihao Date: Wed, 14 May 2025 22:32:22 +0800 Subject: [PATCH 01/19] added router params and button to read and change language in url --- src/pages/sicp/Sicp.tsx | 36 ++++++++++++++++++++++++++++++------ src/routes/routerConfig.tsx | 2 +- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/pages/sicp/Sicp.tsx b/src/pages/sicp/Sicp.tsx index 73798b81ca..407bbc8257 100644 --- a/src/pages/sicp/Sicp.tsx +++ b/src/pages/sicp/Sicp.tsx @@ -2,6 +2,7 @@ import 'katex/dist/katex.min.css'; import { Button, Classes, NonIdealState, Spinner } from '@blueprintjs/core'; import classNames from 'classnames'; +import path from 'path'; import React, { useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useLocation, useNavigate, useParams } from 'react-router'; @@ -40,7 +41,7 @@ const Sicp: React.FC = () => { const [data, setData] = useState(<>); const [loading, setLoading] = useState(false); const [active, setActive] = useState('0'); - const { section } = useParams<{ section: string }>(); + const { lang, section } = useParams<{ lang: string; section: string }>(); const parentRef = useRef(null); const refs = useRef>({}); const navigate = useNavigate(); @@ -95,7 +96,7 @@ const Sicp: React.FC = () => { * the main application navbar. Navigate replace logic is used to allow the * user to still use the browser back button to navigate the app. */ - navigate(`/sicpjs/${readSicpSectionLocalStorage()}`, { replace: true }); + navigate(path.join('sicpjs', readSicpSectionLocalStorage()), { replace: true }); return; } @@ -106,7 +107,7 @@ const Sicp: React.FC = () => { setLoading(true); - fetch(baseUrl + section + extension) + fetch(baseUrl + lang + '/' + section + extension) .then(response => { if (!response.ok) { throw Error(response.statusText); @@ -117,7 +118,7 @@ const Sicp: React.FC = () => { try { const newData = parseArr(myJson, refs); // Might throw error setData(newData); - setSicpSectionLocalStorage(section); // Sets local storage if valid page + setSicpSectionLocalStorage(path.join(lang || 'en', section)); // Sets local storage if valid page } catch (error) { throw new ParseJsonError(error.message); } @@ -139,7 +140,7 @@ const Sicp: React.FC = () => { .finally(() => { setLoading(false); }); - }, [section, navigate]); + }, [lang, section, navigate]); // Scroll to correct position React.useEffect(() => { @@ -164,10 +165,32 @@ const Sicp: React.FC = () => { dispatch(WorkspaceActions.resetWorkspace('sicp')); dispatch(WorkspaceActions.toggleUsingSubst(false, 'sicp')); }; + + const handleLanguageToggle = () => { + const newLang = lang === 'en' ? 'zh_CN' : 'en'; + navigate(`/sicpjs/${newLang}/${section}`); + }; + const handleNavigation = (sect: string) => { - navigate('/sicpjs/' + sect); + navigate('/sicpjs/' + `${lang}/` + sect); }; + // Language toggle button with fixed position + const languageToggle = ( +
+ +
+ ); + // `section` is defined due to the navigate logic in the useEffect above const navigationButtons = (
@@ -187,6 +210,7 @@ const Sicp: React.FC = () => { > + {languageToggle} {loading ? (
{loadingComponent}
) : section === 'index' ? ( diff --git a/src/routes/routerConfig.tsx b/src/routes/routerConfig.tsx index bcf5d326fe..689fc3a9c5 100644 --- a/src/routes/routerConfig.tsx +++ b/src/routes/routerConfig.tsx @@ -49,7 +49,7 @@ const Features = () => import('../pages/featureFlags/FeatureFlags'); const commonChildrenRoutes: RouteObject[] = [ { path: 'contributors', lazy: Contributors }, { path: 'callback/github', lazy: GitHubCallback }, - { path: 'sicpjs/:section?', lazy: Sicp }, + { path: 'sicpjs/:lang/:section?', lazy: Sicp }, { path: 'features', lazy: Features } ]; From f796bda836a3320c8d7caa2fe0b639669d1d1211 Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Tue, 20 May 2025 20:48:13 +0800 Subject: [PATCH 02/19] put textbook lang selection into local storage instead of the url now lang in url only changes the lang in local storage and redirects to the original page --- src/features/sicp/utils/SicpUtils.ts | 12 ++++++++++ src/pages/sicp/Sicp.tsx | 36 ++++++++++++++++++++++++---- src/routes/routerConfig.tsx | 3 ++- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/features/sicp/utils/SicpUtils.ts b/src/features/sicp/utils/SicpUtils.ts index cc347256b1..acbdd56117 100644 --- a/src/features/sicp/utils/SicpUtils.ts +++ b/src/features/sicp/utils/SicpUtils.ts @@ -11,3 +11,15 @@ export const readSicpSectionLocalStorage = () => { const data = readLocalStorage(SICP_CACHE_KEY, SICP_INDEX); return data; }; + +export const SICP_DEF_TB_LANG = 'en'; +export const SICP_TB_LANG_KEY = 'sicp-textbook-lang'; + +export const setSicpLangLocalStorage = (value: string) => { + setLocalStorage(SICP_TB_LANG_KEY, value); +}; + +export const readSicpLangLocalStorage = () => { + const data = readLocalStorage(SICP_TB_LANG_KEY, SICP_DEF_TB_LANG); + return data; +}; diff --git a/src/pages/sicp/Sicp.tsx b/src/pages/sicp/Sicp.tsx index 407bbc8257..4e8ccd3f30 100644 --- a/src/pages/sicp/Sicp.tsx +++ b/src/pages/sicp/Sicp.tsx @@ -15,9 +15,12 @@ import { SicpSection } from 'src/features/sicp/chatCompletion/chatCompletion'; import { parseArr, ParseJsonError } from 'src/features/sicp/parser/ParseJson'; import { getNext, getPrev } from 'src/features/sicp/TableOfContentsHelper'; import { + readSicpLangLocalStorage, readSicpSectionLocalStorage, + setSicpLangLocalStorage, setSicpSectionLocalStorage, SICP_CACHE_KEY, + SICP_DEF_TB_LANG, SICP_INDEX } from 'src/features/sicp/utils/SicpUtils'; @@ -41,7 +44,8 @@ const Sicp: React.FC = () => { const [data, setData] = useState(<>); const [loading, setLoading] = useState(false); const [active, setActive] = useState('0'); - const { lang, section } = useParams<{ lang: string; section: string }>(); + const { param_lang, section } = useParams<{ param_lang:string, section: string }>(); + const [lang, setLang] = useState(readSicpLangLocalStorage()); const parentRef = useRef(null); const refs = useRef>({}); const navigate = useNavigate(); @@ -90,6 +94,23 @@ const Sicp: React.FC = () => { // Handle loading of latest viewed section and fetch json data React.useEffect(() => { + const valid_langs = ['en', 'zh_CN']; + if (section && valid_langs.includes(section) || param_lang) { + const plang = param_lang ? param_lang : (section ? section : SICP_DEF_TB_LANG); + if (!valid_langs.includes(plang)) { + setLang(SICP_DEF_TB_LANG); + setSicpLangLocalStorage(SICP_DEF_TB_LANG); + } else { + setLang(plang); + setSicpLangLocalStorage(plang); + } + if (section && valid_langs.includes(section)) { + navigate(`/sicpjs/${SICP_INDEX}`, { replace: true }); + } else { + navigate(`/sicpjs/${section}`, { replace: true }); + } + return; + } if (!section) { /** * Handles rerouting to the latest viewed section when clicking from @@ -107,6 +128,10 @@ const Sicp: React.FC = () => { setLoading(true); + if (!valid_langs.includes(lang)) { + setLang(SICP_DEF_TB_LANG); + setSicpLangLocalStorage(SICP_DEF_TB_LANG); + } fetch(baseUrl + lang + '/' + section + extension) .then(response => { if (!response.ok) { @@ -118,7 +143,7 @@ const Sicp: React.FC = () => { try { const newData = parseArr(myJson, refs); // Might throw error setData(newData); - setSicpSectionLocalStorage(path.join(lang || 'en', section)); // Sets local storage if valid page + setSicpSectionLocalStorage(section); // Sets local storage if valid page } catch (error) { throw new ParseJsonError(error.message); } @@ -140,7 +165,7 @@ const Sicp: React.FC = () => { .finally(() => { setLoading(false); }); - }, [lang, section, navigate]); + }, [param_lang, section, lang, navigate]); // Scroll to correct position React.useEffect(() => { @@ -168,11 +193,12 @@ const Sicp: React.FC = () => { const handleLanguageToggle = () => { const newLang = lang === 'en' ? 'zh_CN' : 'en'; - navigate(`/sicpjs/${newLang}/${section}`); + setLang(newLang); + setSicpLangLocalStorage(newLang); }; const handleNavigation = (sect: string) => { - navigate('/sicpjs/' + `${lang}/` + sect); + navigate('/sicpjs/' + sect); }; // Language toggle button with fixed position diff --git a/src/routes/routerConfig.tsx b/src/routes/routerConfig.tsx index 689fc3a9c5..0441990f9f 100644 --- a/src/routes/routerConfig.tsx +++ b/src/routes/routerConfig.tsx @@ -49,7 +49,8 @@ const Features = () => import('../pages/featureFlags/FeatureFlags'); const commonChildrenRoutes: RouteObject[] = [ { path: 'contributors', lazy: Contributors }, { path: 'callback/github', lazy: GitHubCallback }, - { path: 'sicpjs/:lang/:section?', lazy: Sicp }, + { path: 'sicpjs/:section?', lazy: Sicp }, + { path: 'sicpjs/:param_lang/:section?', lazy: Sicp }, { path: 'features', lazy: Features } ]; From da13343277e2205bbff4bb3719067e7bb097237c Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Thu, 22 May 2025 20:46:09 +0800 Subject: [PATCH 03/19] fetch toc based on textbook's lang --- src/features/sicp/data/toc.json | 729 ----------------------- src/pages/sicp/subcomponents/SicpToc.tsx | 60 +- 2 files changed, 50 insertions(+), 739 deletions(-) delete mode 100644 src/features/sicp/data/toc.json diff --git a/src/features/sicp/data/toc.json b/src/features/sicp/data/toc.json deleted file mode 100644 index 7df6923f76..0000000000 --- a/src/features/sicp/data/toc.json +++ /dev/null @@ -1,729 +0,0 @@ -[ - { "id": 0, "hasCaret": false, "label": "Foreword", "nodeData": "foreword02" }, - { - "id": 1, - "hasCaret": false, - "label": "Foreword to Structure and Interpretation of Computer Programs, 1984", - "nodeData": "foreword84" - }, - { "id": 2, "hasCaret": false, "label": "Preface", "nodeData": "prefaces03" }, - { - "id": 3, - "hasCaret": false, - "label": "Prefaces to Structure and Interpretation of Computer Programs, 1996 & 1984", - "nodeData": "prefaces96" - }, - { "id": 4, "hasCaret": false, "label": "Acknowledgments", "nodeData": "acknowledgements" }, - { - "id": 6, - "hasCaret": true, - "label": "1 Building Abstractions with Functions", - "nodeData": "1", - "childNodes": [ - { - "id": 7, - "hasCaret": true, - "label": "1.1 The Elements of Programming", - "nodeData": "1.1", - "childNodes": [ - { "id": 8, "hasCaret": false, "label": "1.1.1 Expressions", "nodeData": "1.1.1" }, - { - "id": 9, - "hasCaret": false, - "label": "1.1.2 Naming and the Environment", - "nodeData": "1.1.2" - }, - { - "id": 10, - "hasCaret": false, - "label": "1.1.3 Evaluating Operator Combinations", - "nodeData": "1.1.3" - }, - { "id": 11, "hasCaret": false, "label": "1.1.4 Compound Functions", "nodeData": "1.1.4" }, - { - "id": 12, - "hasCaret": false, - "label": "1.1.5 The Substitution Model for Function Application", - "nodeData": "1.1.5" - }, - { - "id": 13, - "hasCaret": false, - "label": "1.1.6 Conditional Expressions and Predicates", - "nodeData": "1.1.6" - }, - { - "id": 14, - "hasCaret": false, - "label": "1.1.7 Example: Square Roots by Newton's Method", - "nodeData": "1.1.7" - }, - { - "id": 15, - "hasCaret": false, - "label": "1.1.8 Functions as Black-Box Abstractions", - "nodeData": "1.1.8" - } - ] - }, - { - "id": 16, - "hasCaret": true, - "label": "1.2 Functions and the Processes They Generate", - "nodeData": "1.2", - "childNodes": [ - { - "id": 17, - "hasCaret": false, - "label": "1.2.1 Linear Recursion and Iteration", - "nodeData": "1.2.1" - }, - { "id": 18, "hasCaret": false, "label": "1.2.2 Tree Recursion", "nodeData": "1.2.2" }, - { "id": 19, "hasCaret": false, "label": "1.2.3 Orders of Growth", "nodeData": "1.2.3" }, - { "id": 20, "hasCaret": false, "label": "1.2.4 Exponentiation", "nodeData": "1.2.4" }, - { - "id": 21, - "hasCaret": false, - "label": "1.2.5 Greatest Common Divisors", - "nodeData": "1.2.5" - }, - { - "id": 22, - "hasCaret": false, - "label": "1.2.6 Example: Testing for Primality", - "nodeData": "1.2.6" - } - ] - }, - { - "id": 23, - "hasCaret": true, - "label": "1.3 Formulating Abstractions with Higher-Order Functions", - "nodeData": "1.3", - "childNodes": [ - { - "id": 24, - "hasCaret": false, - "label": "1.3.1 Functions as Arguments", - "nodeData": "1.3.1" - }, - { - "id": 25, - "hasCaret": false, - "label": "1.3.2 Constructing Functions using Lambda Expressions", - "nodeData": "1.3.2" - }, - { - "id": 26, - "hasCaret": false, - "label": "1.3.3 Functions as General Methods", - "nodeData": "1.3.3" - }, - { - "id": 27, - "hasCaret": false, - "label": "1.3.4 Functions as Returned Values", - "nodeData": "1.3.4" - } - ] - } - ] - }, - { - "id": 28, - "hasCaret": true, - "label": "2 Building Abstractions with Data", - "nodeData": "2", - "childNodes": [ - { - "id": 29, - "hasCaret": true, - "label": "2.1 Introduction to Data Abstraction", - "nodeData": "2.1", - "childNodes": [ - { - "id": 30, - "hasCaret": false, - "label": "2.1.1 Example: Arithmetic Operations for Rational Numbers", - "nodeData": "2.1.1" - }, - { - "id": 31, - "hasCaret": false, - "label": "2.1.2 Abstraction Barriers", - "nodeData": "2.1.2" - }, - { - "id": 32, - "hasCaret": false, - "label": "2.1.3 What Is Meant by Data?", - "nodeData": "2.1.3" - }, - { - "id": 33, - "hasCaret": false, - "label": "2.1.4 Extended Exercise: Interval Arithmetic", - "nodeData": "2.1.4" - } - ] - }, - { - "id": 34, - "hasCaret": true, - "label": "2.2 Hierarchical Data and the Closure Property", - "nodeData": "2.2", - "childNodes": [ - { - "id": 35, - "hasCaret": false, - "label": "2.2.1 Representing Sequences", - "nodeData": "2.2.1" - }, - { - "id": 36, - "hasCaret": false, - "label": "2.2.2 Hierarchical Structures", - "nodeData": "2.2.2" - }, - { - "id": 37, - "hasCaret": false, - "label": "2.2.3 Sequences as Conventional Interfaces", - "nodeData": "2.2.3" - }, - { - "id": 38, - "hasCaret": false, - "label": "2.2.4 Example: A Picture Language", - "nodeData": "2.2.4" - } - ] - }, - { - "id": 39, - "hasCaret": true, - "label": "2.3 Symbolic Data", - "nodeData": "2.3", - "childNodes": [ - { "id": 40, "hasCaret": false, "label": "2.3.1 Strings", "nodeData": "2.3.1" }, - { - "id": 41, - "hasCaret": false, - "label": "2.3.2 Example: Symbolic Differentiation", - "nodeData": "2.3.2" - }, - { - "id": 42, - "hasCaret": false, - "label": "2.3.3 Example: Representing Sets", - "nodeData": "2.3.3" - }, - { - "id": 43, - "hasCaret": false, - "label": "2.3.4 Example: Huffman Encoding Trees", - "nodeData": "2.3.4" - } - ] - }, - { - "id": 44, - "hasCaret": true, - "label": "2.4 Multiple Representations for Abstract Data", - "nodeData": "2.4", - "childNodes": [ - { - "id": 45, - "hasCaret": false, - "label": "2.4.1 Representations for Complex Numbers", - "nodeData": "2.4.1" - }, - { "id": 46, "hasCaret": false, "label": "2.4.2 Tagged data", "nodeData": "2.4.2" }, - { - "id": 47, - "hasCaret": false, - "label": "2.4.3 Data-Directed Programming and Additivity", - "nodeData": "2.4.3" - } - ] - }, - { - "id": 48, - "hasCaret": true, - "label": "2.5 Systems with Generic Operations", - "nodeData": "2.5", - "childNodes": [ - { - "id": 49, - "hasCaret": false, - "label": "2.5.1 Generic Arithmetic Operations", - "nodeData": "2.5.1" - }, - { - "id": 50, - "hasCaret": false, - "label": "2.5.2 Combining Data of Different Types", - "nodeData": "2.5.2" - }, - { - "id": 51, - "hasCaret": false, - "label": "2.5.3 Example: Symbolic Algebra", - "nodeData": "2.5.3" - } - ] - } - ] - }, - { - "id": 52, - "hasCaret": true, - "label": "3 Modularity, Objects, and State", - "nodeData": "3", - "childNodes": [ - { - "id": 53, - "hasCaret": true, - "label": "3.1 Assignment and Local State", - "nodeData": "3.1", - "childNodes": [ - { - "id": 54, - "hasCaret": false, - "label": "3.1.1 Local State Variables", - "nodeData": "3.1.1" - }, - { - "id": 55, - "hasCaret": false, - "label": "3.1.2 The Benefits of Introducing Assignment", - "nodeData": "3.1.2" - }, - { - "id": 56, - "hasCaret": false, - "label": "3.1.3 The Costs of Introducing Assignment", - "nodeData": "3.1.3" - } - ] - }, - { - "id": 57, - "hasCaret": true, - "label": "3.2 The Environment Model of Evaluation", - "nodeData": "3.2", - "childNodes": [ - { - "id": 58, - "hasCaret": false, - "label": "3.2.1 The Rules for Evaluation", - "nodeData": "3.2.1" - }, - { - "id": 59, - "hasCaret": false, - "label": "3.2.2 Applying Simple Functions", - "nodeData": "3.2.2" - }, - { - "id": 60, - "hasCaret": false, - "label": "3.2.3 Frames as the Repository of Local State", - "nodeData": "3.2.3" - }, - { - "id": 61, - "hasCaret": false, - "label": "3.2.4 Internal Declarations", - "nodeData": "3.2.4" - } - ] - }, - { - "id": 62, - "hasCaret": true, - "label": "3.3 Modeling with Mutable Data", - "nodeData": "3.3", - "childNodes": [ - { - "id": 63, - "hasCaret": false, - "label": "3.3.1 Mutable List Structure", - "nodeData": "3.3.1" - }, - { - "id": 64, - "hasCaret": false, - "label": "3.3.2 Representing Queues", - "nodeData": "3.3.2" - }, - { - "id": 65, - "hasCaret": false, - "label": "3.3.3 Representing Tables", - "nodeData": "3.3.3" - }, - { - "id": 66, - "hasCaret": false, - "label": "3.3.4 A Simulator for Digital Circuits", - "nodeData": "3.3.4" - }, - { - "id": 67, - "hasCaret": false, - "label": "3.3.5 Propagation of Constraints", - "nodeData": "3.3.5" - } - ] - }, - { - "id": 68, - "hasCaret": true, - "label": "3.4 Concurrency: Time Is of the Essence", - "nodeData": "3.4", - "childNodes": [ - { - "id": 69, - "hasCaret": false, - "label": "3.4.1 The Nature of Time in Concurrent Systems", - "nodeData": "3.4.1" - }, - { - "id": 70, - "hasCaret": false, - "label": "3.4.2 Mechanisms for Controlling Concurrency", - "nodeData": "3.4.2" - } - ] - }, - { - "id": 71, - "hasCaret": true, - "label": "3.5 Streams", - "nodeData": "3.5", - "childNodes": [ - { - "id": 72, - "hasCaret": false, - "label": "3.5.1 Streams Are Delayed Lists", - "nodeData": "3.5.1" - }, - { "id": 73, "hasCaret": false, "label": "3.5.2 Infinite Streams", "nodeData": "3.5.2" }, - { - "id": 74, - "hasCaret": false, - "label": "3.5.3 Exploiting the Stream Paradigm", - "nodeData": "3.5.3" - }, - { - "id": 75, - "hasCaret": false, - "label": "3.5.4 Streams and Delayed Evaluation", - "nodeData": "3.5.4" - }, - { - "id": 76, - "hasCaret": false, - "label": "3.5.5 Modularity of Functional Programs and Modularity of Objects", - "nodeData": "3.5.5" - } - ] - } - ] - }, - { - "id": 77, - "hasCaret": true, - "label": "4 Metalinguistic Abstraction", - "nodeData": "4", - "childNodes": [ - { - "id": 78, - "hasCaret": true, - "label": "4.1 The Metacircular Evaluator", - "nodeData": "4.1", - "childNodes": [ - { - "id": 79, - "hasCaret": false, - "label": "4.1.1 The Core of the Evaluator", - "nodeData": "4.1.1" - }, - { - "id": 80, - "hasCaret": false, - "label": "4.1.2 Representing Components", - "nodeData": "4.1.2" - }, - { - "id": 81, - "hasCaret": false, - "label": "4.1.3 Evaluator Data Structures", - "nodeData": "4.1.3" - }, - { - "id": 82, - "hasCaret": false, - "label": "4.1.4 Running the Evaluator as a Program", - "nodeData": "4.1.4" - }, - { "id": 83, "hasCaret": false, "label": "4.1.5 Data as Programs", "nodeData": "4.1.5" }, - { - "id": 84, - "hasCaret": false, - "label": "4.1.6 Internal Declarations", - "nodeData": "4.1.6" - }, - { - "id": 85, - "hasCaret": false, - "label": "4.1.7 Separating Syntactic Analysis from Execution", - "nodeData": "4.1.7" - } - ] - }, - { - "id": 86, - "hasCaret": true, - "label": "4.2 Lazy Evaluation", - "nodeData": "4.2", - "childNodes": [ - { - "id": 87, - "hasCaret": false, - "label": "4.2.1 Normal Order and Applicative Order", - "nodeData": "4.2.1" - }, - { - "id": 88, - "hasCaret": false, - "label": "4.2.2 An Interpreter with Lazy Evaluation", - "nodeData": "4.2.2" - }, - { - "id": 89, - "hasCaret": false, - "label": "4.2.3 Streams as Lazy Lists", - "nodeData": "4.2.3" - } - ] - }, - { - "id": 90, - "hasCaret": true, - "label": "4.3 Nondeterministic Computing", - "nodeData": "4.3", - "childNodes": [ - { "id": 91, "hasCaret": false, "label": "4.3.1 Search and amb", "nodeData": "4.3.1" }, - { - "id": 92, - "hasCaret": false, - "label": "4.3.2 Examples of Nondeterministic Programs", - "nodeData": "4.3.2" - }, - { - "id": 93, - "hasCaret": false, - "label": "4.3.3 Implementing the amb Evaluator", - "nodeData": "4.3.3" - } - ] - }, - { - "id": 94, - "hasCaret": true, - "label": "4.4 Logic Programming", - "nodeData": "4.4", - "childNodes": [ - { - "id": 95, - "hasCaret": false, - "label": "4.4.1 Deductive Information Retrieval", - "nodeData": "4.4.1" - }, - { - "id": 96, - "hasCaret": false, - "label": "4.4.2 How the Query System Works", - "nodeData": "4.4.2" - }, - { - "id": 97, - "hasCaret": false, - "label": "4.4.3 Is Logic Programming Mathematical Logic?", - "nodeData": "4.4.3" - }, - { - "id": 98, - "hasCaret": false, - "label": "4.4.4 Implementing the Query System", - "nodeData": "4.4.4" - } - ] - } - ] - }, - { - "id": 99, - "hasCaret": true, - "label": "5 Computing with Register Machines", - "nodeData": "5", - "childNodes": [ - { - "id": 100, - "hasCaret": true, - "label": "5.1 Designing Register Machines", - "nodeData": "5.1", - "childNodes": [ - { - "id": 101, - "hasCaret": false, - "label": "5.1.1 A Language for Describing Register Machines", - "nodeData": "5.1.1" - }, - { - "id": 102, - "hasCaret": false, - "label": "5.1.2 Abstraction in Machine Design", - "nodeData": "5.1.2" - }, - { "id": 103, "hasCaret": false, "label": "5.1.3 Subroutines", "nodeData": "5.1.3" }, - { - "id": 104, - "hasCaret": false, - "label": "5.1.4 Using a Stack to Implement Recursion", - "nodeData": "5.1.4" - }, - { - "id": 105, - "hasCaret": false, - "label": "5.1.5 Instruction Summary", - "nodeData": "5.1.5" - } - ] - }, - { - "id": 106, - "hasCaret": true, - "label": "5.2 A Register-Machine Simulator", - "nodeData": "5.2", - "childNodes": [ - { "id": 107, "hasCaret": false, "label": "5.2.1 The Machine Model", "nodeData": "5.2.1" }, - { "id": 108, "hasCaret": false, "label": "5.2.2 The Assembler", "nodeData": "5.2.2" }, - { - "id": 109, - "hasCaret": false, - "label": "5.2.3 Instructions and Their Execution Functions", - "nodeData": "5.2.3" - }, - { - "id": 110, - "hasCaret": false, - "label": "5.2.4 Monitoring Machine Performance", - "nodeData": "5.2.4" - } - ] - }, - { - "id": 111, - "hasCaret": true, - "label": "5.3 Storage Allocation and Garbage Collection", - "nodeData": "5.3", - "childNodes": [ - { "id": 112, "hasCaret": false, "label": "5.3.1 Memory as Vectors", "nodeData": "5.3.1" }, - { - "id": 113, - "hasCaret": false, - "label": "5.3.2 Maintaining the Illusion of Infinite Memory", - "nodeData": "5.3.2" - } - ] - }, - { - "id": 114, - "hasCaret": true, - "label": "5.4 The Explicit-Control Evaluator", - "nodeData": "5.4", - "childNodes": [ - { - "id": 115, - "hasCaret": false, - "label": "5.4.1 The Dispatcher and Basic Evaluation", - "nodeData": "5.4.1" - }, - { - "id": 116, - "hasCaret": false, - "label": "5.4.2 Evaluating Function Applications", - "nodeData": "5.4.2" - }, - { - "id": 117, - "hasCaret": false, - "label": "5.4.3 Blocks, Assignments, and Declarations", - "nodeData": "5.4.3" - }, - { - "id": 118, - "hasCaret": false, - "label": "5.4.4 Running the Evaluator", - "nodeData": "5.4.4" - } - ] - }, - { - "id": 119, - "hasCaret": true, - "label": "5.5 Compilation", - "nodeData": "5.5", - "childNodes": [ - { - "id": 120, - "hasCaret": false, - "label": "5.5.1 Structure of the Compiler", - "nodeData": "5.5.1" - }, - { - "id": 121, - "hasCaret": false, - "label": "5.5.2 Compiling Components", - "nodeData": "5.5.2" - }, - { - "id": 122, - "hasCaret": false, - "label": "5.5.3 Compiling Applications and Return Statements", - "nodeData": "5.5.3" - }, - { - "id": 123, - "hasCaret": false, - "label": "5.5.4 Combining Instruction Sequences", - "nodeData": "5.5.4" - }, - { - "id": 124, - "hasCaret": false, - "label": "5.5.5 An Example of Compiled Code", - "nodeData": "5.5.5" - }, - { - "id": 125, - "hasCaret": false, - "label": "5.5.6 Lexical Addressing", - "nodeData": "5.5.6" - }, - { - "id": 126, - "hasCaret": false, - "label": "5.5.7 Interfacing Compiled Code to the Evaluator", - "nodeData": "5.5.7" - } - ] - } - ] - }, - { "id": 127, "hasCaret": false, "label": "References", "nodeData": "references" }, - { "id": 128, "hasCaret": false, "label": "About the SICP JS Project", "nodeData": "making-of" } -] diff --git a/src/pages/sicp/subcomponents/SicpToc.tsx b/src/pages/sicp/subcomponents/SicpToc.tsx index 84fbced81f..494a87001d 100644 --- a/src/pages/sicp/subcomponents/SicpToc.tsx +++ b/src/pages/sicp/subcomponents/SicpToc.tsx @@ -1,9 +1,14 @@ import { Tree, TreeNodeInfo } from '@blueprintjs/core'; +import { NonIdealState, Spinner } from '@blueprintjs/core'; import { cloneDeep } from 'lodash'; import React, { useState } from 'react'; import { useNavigate } from 'react-router'; +import Constants from 'src/commons/utils/Constants'; +import getSicpError, { SicpErrorType } from 'src/features/sicp/errors/SicpErrors'; +import { readSicpLangLocalStorage } from 'src/features/sicp/utils/SicpUtils'; -import toc from '../../../features/sicp/data/toc.json'; +const baseUrl = Constants.sicpBackendUrl + 'json/'; +const loadingComponent = } />; type TocProps = OwnProps; @@ -14,8 +19,9 @@ type OwnProps = { /** * Table of contents of SICP. */ -const SicpToc: React.FC = props => { - const [sidebarContent, setSidebarContent] = useState(toc as TreeNodeInfo[]); + +const Toc: React.FC<{ toc: TreeNodeInfo[], props: TocProps }> = ({toc, props}) => { + const [sidebarContent, setSidebarContent] = useState(toc); const navigate = useNavigate(); const handleNodeExpand = (_node: TreeNodeInfo, path: integer[]) => { @@ -40,15 +46,49 @@ const SicpToc: React.FC = props => { [navigate, props] ); + return ( + + ); +}; + +const SicpToc: React.FC = props => { + const [data, setData] = useState(<>); + const [loading, setLoading] = useState(true); + + React.useEffect(() => { + fetch(baseUrl + readSicpLangLocalStorage() + '/toc.json') + .then(response => { + if (!response.ok) { + throw Error(response.statusText); + } + return response.json(); + }) + .then(toc => { + const newData = ( + + ); + setData(newData); + }) + .catch(error => { + console.log(error); + setData(getSicpError(SicpErrorType.UNEXPECTED_ERROR)); + }) + .finally(() => { + setLoading(false); + }); + }, [props]); + return (
- + {loading ? ( +
{loadingComponent}
+ ): data}
); }; From 8546da105409a096c3c58ca6c9f7bb3406ba7986 Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Thu, 29 May 2025 18:21:23 +0800 Subject: [PATCH 04/19] update toc react component whenever textbook lang is changed --- src/features/sicp/utils/SicpUtils.ts | 1 + src/pages/sicp/Sicp.tsx | 1 + src/pages/sicp/subcomponents/SicpToc.tsx | 32 ++++++++++++++++-------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/features/sicp/utils/SicpUtils.ts b/src/features/sicp/utils/SicpUtils.ts index acbdd56117..1e725f0a55 100644 --- a/src/features/sicp/utils/SicpUtils.ts +++ b/src/features/sicp/utils/SicpUtils.ts @@ -17,6 +17,7 @@ export const SICP_TB_LANG_KEY = 'sicp-textbook-lang'; export const setSicpLangLocalStorage = (value: string) => { setLocalStorage(SICP_TB_LANG_KEY, value); + window.dispatchEvent(new Event('sicp-tb-lang-change')); }; export const readSicpLangLocalStorage = () => { diff --git a/src/pages/sicp/Sicp.tsx b/src/pages/sicp/Sicp.tsx index 4e8ccd3f30..78a59b7392 100644 --- a/src/pages/sicp/Sicp.tsx +++ b/src/pages/sicp/Sicp.tsx @@ -95,6 +95,7 @@ const Sicp: React.FC = () => { // Handle loading of latest viewed section and fetch json data React.useEffect(() => { const valid_langs = ['en', 'zh_CN']; + if (section && valid_langs.includes(section) || param_lang) { const plang = param_lang ? param_lang : (section ? section : SICP_DEF_TB_LANG); if (!valid_langs.includes(plang)) { diff --git a/src/pages/sicp/subcomponents/SicpToc.tsx b/src/pages/sicp/subcomponents/SicpToc.tsx index 494a87001d..1e7d965a8d 100644 --- a/src/pages/sicp/subcomponents/SicpToc.tsx +++ b/src/pages/sicp/subcomponents/SicpToc.tsx @@ -58,37 +58,49 @@ const Toc: React.FC<{ toc: TreeNodeInfo[], props: TocProps }> = ({toc, props}) = }; const SicpToc: React.FC = props => { - const [data, setData] = useState(<>); + const [lang, setLang] = useState(readSicpLangLocalStorage()); + const [toc, setToc] = useState([] as TreeNodeInfo[]); const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); React.useEffect(() => { - fetch(baseUrl + readSicpLangLocalStorage() + '/toc.json') + const handleLangChange = () => { + setLang(readSicpLangLocalStorage()); + } + window.addEventListener('sicp-tb-lang-change', handleLangChange); + return () => window.removeEventListener('sicp-tb-lang-change', handleLangChange) + }, []); + + React.useEffect(() => { + setLoading(true); + fetch(baseUrl + lang + '/toc.json') .then(response => { if (!response.ok) { throw Error(response.statusText); } return response.json(); }) - .then(toc => { - const newData = ( - - ); - setData(newData); + .then(json => { + setToc(json as TreeNodeInfo[]); }) .catch(error => { console.log(error); - setData(getSicpError(SicpErrorType.UNEXPECTED_ERROR)); + setError(true); }) .finally(() => { setLoading(false); }); - }, [props]); + }, [lang]); return (
{loading ? (
{loadingComponent}
- ): data} + ) : error ? ( + getSicpError(SicpErrorType.UNEXPECTED_ERROR) + ) : ( + + )}
); }; From 32df0bd5d5091ffc9de4f62693dfd43d0782ff80 Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Thu, 29 May 2025 18:32:50 +0800 Subject: [PATCH 05/19] put the lang switch at the bottom so it does not cover the toc menu --- src/pages/sicp/Sicp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/sicp/Sicp.tsx b/src/pages/sicp/Sicp.tsx index 78a59b7392..095519295a 100644 --- a/src/pages/sicp/Sicp.tsx +++ b/src/pages/sicp/Sicp.tsx @@ -209,7 +209,7 @@ const Sicp: React.FC = () => { position: 'sticky', top: '20px', left: '20px', - zIndex: 1000 + zIndex: 0 }} >

diff --git a/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap b/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap index c62b6bd580..a838b89fae 100644 --- a/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap +++ b/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap @@ -5,631 +5,61 @@ exports[`Sicp toc renders correctly 1`] = ` className="sicp-toc" >
-
    -
  • -
    - - - Foreword - -
    -
    -
    -
    -
  • -
  • -
    - - - Foreword to Structure and Interpretation of Computer Programs, 1984 - -
    -
    -
    -
    -
  • -
  • -
    - - - Preface - -
    -
    -
    -
    -
  • -
  • -
    - - - Prefaces to Structure and Interpretation of Computer Programs, 1996 & 1984 - -
    -
    -
    -
    -
  • -
  • -
    - - - Acknowledgments - -
    -
    -
    -
    -
  • -
  • -
    - - - - Expand group - - - - - - 1 Building Abstractions with Functions - -
    -
    -
    -
    -
  • -
  • -
    - - - - Expand group - - - - - - 2 Building Abstractions with Data - -
    -
    -
    -
    -
  • -
  • - - - - Expand group - - - - - - 3 Modularity, Objects, and State - -
    -
    -
    -
  • -
  • -
    - - - Expand group - - - - - 4 Metalinguistic Abstraction - -
    -
    -
    -
    -
  • -
  • -
    - - - - Expand group - - - - 5 Computing with Register Machines - -
    -
    -
    +
    -
  • -
  • +
    -
    - - - References - -
    -
    -
    -
    -
  • -
  • -
    - - - About the SICP JS Project - -
    -
    -
    -
    -
  • -
+ Loading Content + +
+ `; From 17c274562a3bb245e4a5d42d1e96ac2d02774548 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 10 Aug 2025 14:45:51 +0800 Subject: [PATCH 12/19] Update conflicting snapshots post-merge --- .../__snapshots__/SicpIndexPage.test.tsx.snap | 644 +----------------- .../__snapshots__/SicpToc.test.tsx.snap | 644 +----------------- 2 files changed, 74 insertions(+), 1214 deletions(-) diff --git a/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpIndexPage.test.tsx.snap b/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpIndexPage.test.tsx.snap index 0ad1b8fbb4..b2ec63dffa 100644 --- a/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpIndexPage.test.tsx.snap +++ b/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpIndexPage.test.tsx.snap @@ -74,631 +74,61 @@ exports[`Sicp index page 1`] = ` className="sicp-toc" >
-
    -
  • -
    - - - Foreword - -
    -
    -
    -
    -
  • -
  • -
    - - - Foreword to Structure and Interpretation of Computer Programs, 1984 - -
    -
    -
    -
    -
  • -
  • -
    - - - Preface - -
    -
    -
    -
    -
  • -
  • -
    - - - Prefaces to Structure and Interpretation of Computer Programs, 1996 & 1984 - -
    -
    -
    -
    -
  • -
  • -
    - - - Acknowledgments - -
    -
    -
    -
    -
  • -
  • -
    - - - - Expand group - - - - - - 1 Building Abstractions with Functions - -
    -
    -
    -
    -
  • -
  • -
    - - - - Expand group - - - - - - 2 Building Abstractions with Data - -
    -
    -
    -
    -
  • -
  • - - - - Expand group - - - - - - 3 Modularity, Objects, and State - -
    -
    -
    -
  • -
  • -
    - - - Expand group - - - - - 4 Metalinguistic Abstraction - -
    -
    -
    -
    -
  • -
  • -
    - - - - Expand group - - - - 5 Computing with Register Machines - -
    -
    -
    -
    -
  • -
  • -
    - - - References - +
-
-
-
- -
  • +
    -
    - - - About the SICP JS Project - -
    -
    -
    -
    -
  • - + Loading Content + +
    +
    diff --git a/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap b/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap index 7588cee636..0000b2b1cb 100644 --- a/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap +++ b/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap @@ -5,631 +5,61 @@ exports[`Sicp toc renders correctly 1`] = ` className="sicp-toc" >
    -
      -
    • -
      - - - Foreword - -
      -
      -
      -
      -
    • -
    • -
      - - - Foreword to Structure and Interpretation of Computer Programs, 1984 - -
      -
      -
      -
      -
    • -
    • -
      - - - Preface - -
      -
      -
      -
      -
    • -
    • -
      - - - Prefaces to Structure and Interpretation of Computer Programs, 1996 & 1984 - -
      -
      -
      -
      -
    • -
    • -
      - - - Acknowledgments - -
      -
      -
      -
      -
    • -
    • -
      - - - - Expand group - - - - - - 1 Building Abstractions with Functions - -
      -
      -
      -
      -
    • -
    • -
      - - - - Expand group - - - - - - 2 Building Abstractions with Data - -
      -
      -
      -
      -
    • -
    • - - - - Expand group - - - - - - 3 Modularity, Objects, and State - -
      -
      -
      -
    • -
    • -
      - - - Expand group - - - - - 4 Metalinguistic Abstraction - -
      -
      -
      -
      -
    • -
    • -
      - - - - Expand group - - - - 5 Computing with Register Machines - -
      -
      -
      +
      -
    • -
    • +
      -
      - - - References - -
      -
      -
      -
      -
    • -
    • -
      - - - About the SICP JS Project - -
      -
      -
      -
      -
    • -
    + Loading Content + +
    + `; From b9bf2ce52b128f7ebb6860e2ea8371bb35d63207 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 20 Aug 2025 00:08:32 +0800 Subject: [PATCH 13/19] Create SICP language provider --- src/features/sicp/utils/SicpUtils.ts | 20 ++++++--- .../subcomponents/SicpLanguageProvider.tsx | 45 +++++++++++++++++++ 2 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 src/pages/sicp/subcomponents/SicpLanguageProvider.tsx diff --git a/src/features/sicp/utils/SicpUtils.ts b/src/features/sicp/utils/SicpUtils.ts index 1e725f0a55..d33487a795 100644 --- a/src/features/sicp/utils/SicpUtils.ts +++ b/src/features/sicp/utils/SicpUtils.ts @@ -12,15 +12,21 @@ export const readSicpSectionLocalStorage = () => { return data; }; -export const SICP_DEF_TB_LANG = 'en'; -export const SICP_TB_LANG_KEY = 'sicp-textbook-lang'; +const SICP_SUPPORTED_LANGUAGES = ['en', 'zh_CN'] as const satisfies readonly string[]; +export type SicpSupportedLanguage = (typeof SICP_SUPPORTED_LANGUAGES)[number]; +export const SICP_DEFAULT_LANGUAGE: SicpSupportedLanguage = 'en'; -export const setSicpLangLocalStorage = (value: string) => { - setLocalStorage(SICP_TB_LANG_KEY, value); +const sicplanguageKey = 'sicp-textbook-lang'; + +export const persistSicpLanguageToLocalStorage = (value: string) => { + setLocalStorage(sicplanguageKey, value); window.dispatchEvent(new Event('sicp-tb-lang-change')); }; -export const readSicpLangLocalStorage = () => { - const data = readLocalStorage(SICP_TB_LANG_KEY, SICP_DEF_TB_LANG); - return data; +export const getSicpLanguageFromLocalStorage = (): SicpSupportedLanguage | null => { + const value = readLocalStorage(sicplanguageKey, null); + if (!SICP_SUPPORTED_LANGUAGES.includes(value)) { + return null; + } + return value as SicpSupportedLanguage; }; diff --git a/src/pages/sicp/subcomponents/SicpLanguageProvider.tsx b/src/pages/sicp/subcomponents/SicpLanguageProvider.tsx new file mode 100644 index 0000000000..ad72a3926e --- /dev/null +++ b/src/pages/sicp/subcomponents/SicpLanguageProvider.tsx @@ -0,0 +1,45 @@ +import { createContext, useCallback, useContext, useState } from 'react'; +import { + getSicpLanguageFromLocalStorage, + SICP_DEFAULT_LANGUAGE, + type SicpSupportedLanguage +} from 'src/features/sicp/utils/SicpUtils'; + +type SicpLanguageContext = { + sicpLanguage: SicpSupportedLanguage; + setSicpLanguage: (lang: SicpSupportedLanguage) => void; +}; + +const sicpLanguageContext = createContext(undefined); + +export const useSicpLanguageContext = (): SicpLanguageContext => { + const context = useContext(sicpLanguageContext); + if (!context) { + throw new Error('useSicpLanguageContext must be used inside an SicpLanguageContextProvider'); + } + + return context; +}; + +export const SicpLanguageContextProvider: React.FC<{ children: React.ReactNode }> = ({ + children +}) => { + const [lang, setLang] = useState( + getSicpLanguageFromLocalStorage() ?? SICP_DEFAULT_LANGUAGE + ); + + const handleLangChange = useCallback((newLang: SicpSupportedLanguage) => { + setLang(newLang); + }, []); + + return ( + + {children} + + ); +}; From daab656b9be81dc9a08b2b1eef041d2fbf80cc16 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 20 Aug 2025 00:08:46 +0800 Subject: [PATCH 14/19] Decouple language logic from UI in SiCP page --- src/pages/sicp/Sicp.tsx | 56 ++++++++--------------------------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/src/pages/sicp/Sicp.tsx b/src/pages/sicp/Sicp.tsx index ecaee6ef1f..1b98267d2a 100644 --- a/src/pages/sicp/Sicp.tsx +++ b/src/pages/sicp/Sicp.tsx @@ -13,12 +13,9 @@ import { SicpSection } from 'src/features/sicp/chatCompletion/chatCompletion'; import { parseArr, ParseJsonError } from 'src/features/sicp/parser/ParseJson'; import { getNext, getPrev } from 'src/features/sicp/TableOfContentsHelper'; import { - readSicpLangLocalStorage, readSicpSectionLocalStorage, - setSicpLangLocalStorage, setSicpSectionLocalStorage, SICP_CACHE_KEY, - SICP_DEF_TB_LANG, SICP_INDEX } from 'src/features/sicp/utils/SicpUtils'; @@ -26,6 +23,7 @@ import SicpErrorBoundary from '../../features/sicp/errors/SicpErrorBoundary'; import getSicpError, { SicpErrorType } from '../../features/sicp/errors/SicpErrors'; import Chatbot from './subcomponents/chatbot/Chatbot'; import SicpIndexPage from './subcomponents/SicpIndexPage'; +import { useSicpLanguageContext } from './subcomponents/SicpLanguageProvider'; const baseUrl = Constants.sicpBackendUrl + 'json/'; const extension = '.json'; @@ -38,24 +36,12 @@ export const CodeSnippetContext = React.createContext({ const loadingComponent = } />; -const AVAILABLE_SICP_TB_LANGS: readonly string[] = ['en', 'zh_CN']; - -const loadInitialLang = () => { - const saved = readSicpLangLocalStorage(); - if (AVAILABLE_SICP_TB_LANGS.includes(saved)) { - return saved; - } else { - setSicpLangLocalStorage(SICP_DEF_TB_LANG); - return SICP_DEF_TB_LANG; - } -}; - const Sicp: React.FC = () => { const [data, setData] = useState(<>); const [loading, setLoading] = useState(false); const [active, setActive] = useState('0'); - const { paramLang, section } = useParams<{ paramLang: string; section: string }>(); - const [lang, setLang] = useState(loadInitialLang()); + const { section } = useParams<{ section: string }>(); + const { sicpLanguage, setSicpLanguage } = useSicpLanguageContext(); const parentRef = useRef(null); const refs = useRef>({}); const navigate = useNavigate(); @@ -104,23 +90,6 @@ const Sicp: React.FC = () => { // Handle loading of latest viewed section and fetch json data React.useEffect(() => { - if (paramLang || (section && AVAILABLE_SICP_TB_LANGS.includes(section))) { - const pLang = (paramLang ? paramLang : section)!; - if (AVAILABLE_SICP_TB_LANGS.includes(pLang)) { - setLang(pLang); - setSicpLangLocalStorage(pLang); - } else { - setLang(SICP_DEF_TB_LANG); - setSicpLangLocalStorage(SICP_DEF_TB_LANG); - } - if (paramLang) { - navigate(`/sicpjs/${section}`, { replace: true }); - } else { - navigate(`/sicpjs/${readSicpSectionLocalStorage()}`, { replace: true }); - } - return; - } - if (!section) { /** * Handles rerouting to the latest viewed section when clicking from @@ -138,11 +107,7 @@ const Sicp: React.FC = () => { setLoading(true); - if (!AVAILABLE_SICP_TB_LANGS.includes(lang)) { - setLang(SICP_DEF_TB_LANG); - setSicpLangLocalStorage(SICP_DEF_TB_LANG); - } - fetch(baseUrl + lang + '/' + section + extension) + fetch(`${baseUrl}${sicpLanguage}/${section}${extension}`) .then(response => { if (!response.ok) { throw Error(response.statusText); @@ -158,6 +123,7 @@ const Sicp: React.FC = () => { throw new ParseJsonError(error.message); } }) + .catch(error => { console.error(error); @@ -175,7 +141,7 @@ const Sicp: React.FC = () => { .finally(() => { setLoading(false); }); - }, [paramLang, section, lang, navigate]); + }, [section, sicpLanguage, navigate]); // Scroll to correct position React.useEffect(() => { @@ -201,10 +167,8 @@ const Sicp: React.FC = () => { dispatch(WorkspaceActions.toggleUsingSubst(false, 'sicp')); }; - const handleLanguageToggle = () => { - const newLang = lang === 'en' ? 'zh_CN' : 'en'; - setLang(newLang); - setSicpLangLocalStorage(newLang); + const toggleSicpLanguage = () => { + setSicpLanguage(sicpLanguage === 'en' ? 'zh_CN' : 'en'); }; const handleNavigation = (sect: string) => { @@ -221,8 +185,8 @@ const Sicp: React.FC = () => { zIndex: 0 }} > - ); From 1b600e99e7303927fb66ea637a5bc97d7b72bcff Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 20 Aug 2025 00:09:01 +0800 Subject: [PATCH 15/19] Remove second param matcher --- src/routes/routerConfig.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/routerConfig.ts b/src/routes/routerConfig.ts index add196b670..d2b6a4c48f 100644 --- a/src/routes/routerConfig.ts +++ b/src/routes/routerConfig.ts @@ -51,7 +51,6 @@ const commonChildrenRoutes: RouteObject[] = [ { path: 'contributors', lazy: Contributors }, { path: 'callback/github', lazy: GitHubCallback }, { path: 'sicpjs/:section?', lazy: Sicp }, - { path: 'sicpjs/:param_lang/:section?', lazy: Sicp }, { path: 'features', lazy: Features } ]; From f3293601128e6d55999cdb8bb14892e74b459190 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 20 Aug 2025 00:21:15 +0800 Subject: [PATCH 16/19] Use SICP language provider --- src/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 44f5dfd2a6..626a509eee 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -21,6 +21,7 @@ import { store } from 'src/pages/createStore'; import ApplicationWrapper from './commons/application/ApplicationWrapper'; import { createInBrowserFileSystem } from './pages/fileSystem/createInBrowserFileSystem'; +import { SicpLanguageContextProvider } from './pages/sicp/subcomponents/SicpLanguageProvider'; if (Constants.sentryDsn) { Sentry.init({ @@ -61,7 +62,9 @@ createInBrowserFileSystem(store) root.render( - + + + ); From 52b4d5dd996938f613cb3864de32fca3f01f4e00 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 20 Aug 2025 00:21:30 +0800 Subject: [PATCH 17/19] Revert SICP ToC changes and rewrite logic --- src/pages/sicp/subcomponents/SicpToc.tsx | 82 ++++++------------------ 1 file changed, 20 insertions(+), 62 deletions(-) diff --git a/src/pages/sicp/subcomponents/SicpToc.tsx b/src/pages/sicp/subcomponents/SicpToc.tsx index 5f2c3a99b2..407e5ab15c 100644 --- a/src/pages/sicp/subcomponents/SicpToc.tsx +++ b/src/pages/sicp/subcomponents/SicpToc.tsx @@ -1,15 +1,11 @@ import { Tree, TreeNodeInfo } from '@blueprintjs/core'; -import { NonIdealState, Spinner } from '@blueprintjs/core'; import { cloneDeep } from 'lodash'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router'; import Constants from 'src/commons/utils/Constants'; -import { readSicpLangLocalStorage } from 'src/features/sicp/utils/SicpUtils'; import fallbackToc from '../../../features/sicp/data/toc.json'; - -const baseUrl = Constants.sicpBackendUrl + 'json/'; -const loadingComponent = } />; +import { useSicpLanguageContext } from './SicpLanguageProvider'; type TocProps = OwnProps; @@ -20,10 +16,18 @@ type OwnProps = { /** * Table of contents of SICP. */ - -const Toc: React.FC<{ toc: TreeNodeInfo[]; props: TocProps }> = ({ toc, props }) => { - const [sidebarContent, setSidebarContent] = useState(toc); +const SicpToc: React.FC = props => { + const [sidebarContent, setSidebarContent] = useState(fallbackToc as TreeNodeInfo[]); const navigate = useNavigate(); + const { sicpLanguage } = useSicpLanguageContext(); + + useEffect(() => { + const loadLocalizedToc = async () => { + const resp = await fetch(`${Constants.sicpBackendUrl}json/${sicpLanguage}/toc.json`); + return (await resp.json()) as TreeNodeInfo[]; + }; + loadLocalizedToc().then(setSidebarContent).catch(console.error); + }, [sicpLanguage]); const handleNodeExpand = (_node: TreeNodeInfo, path: integer[]) => { const newState = cloneDeep(sidebarContent); @@ -47,61 +51,15 @@ const Toc: React.FC<{ toc: TreeNodeInfo[]; props: TocProps }> = ({ toc, props }) [navigate, props] ); - return ( - - ); -}; - -const SicpToc: React.FC = props => { - const [lang, setLang] = useState(readSicpLangLocalStorage()); - const [toc, setToc] = useState([] as TreeNodeInfo[]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(false); - - React.useEffect(() => { - const handleLangChange = () => { - setLang(readSicpLangLocalStorage()); - }; - window.addEventListener('sicp-tb-lang-change', handleLangChange); - return () => window.removeEventListener('sicp-tb-lang-change', handleLangChange); - }, []); - - React.useEffect(() => { - setLoading(true); - fetch(baseUrl + lang + '/toc.json') - .then(response => { - if (!response.ok) { - throw Error(response.statusText); - } - return response.json(); - }) - .then(json => { - setToc(json as TreeNodeInfo[]); - }) - .catch(error => { - console.log(error); - setError(true); - }) - .finally(() => { - setLoading(false); - }); - }, [lang]); - return (
    - {loading ? ( -
    {loadingComponent}
    - ) : error ? ( - - ) : ( - - )} +
    ); }; From e1e3b665e3ff2ff4d829c168a64d6835664a2710 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 20 Aug 2025 00:21:56 +0800 Subject: [PATCH 18/19] Remove validation module from production --- src/bootstrap/agGrid.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bootstrap/agGrid.ts b/src/bootstrap/agGrid.ts index 6c83b1153f..d215008b19 100644 --- a/src/bootstrap/agGrid.ts +++ b/src/bootstrap/agGrid.ts @@ -26,8 +26,7 @@ const productionModules: readonly Module[] = [ PaginationModule, RowDragModule, TextEditorModule, - TextFilterModule, - ValidationModule + TextFilterModule ]; export const initializeAgGridModules = () => { From 86c2004017b8ca94c2f98cc666a31d8a5092d299 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 20 Aug 2025 00:25:10 +0800 Subject: [PATCH 19/19] Update tests and snapshots --- .../__tests__/SicpIndexPage.test.tsx | 5 +- .../subcomponents/__tests__/SicpToc.test.tsx | 5 +- .../__snapshots__/SicpIndexPage.test.tsx.snap | 644 +++++++++++++++++- .../__snapshots__/SicpToc.test.tsx.snap | 644 +++++++++++++++++- 4 files changed, 1222 insertions(+), 76 deletions(-) diff --git a/src/pages/sicp/subcomponents/__tests__/SicpIndexPage.test.tsx b/src/pages/sicp/subcomponents/__tests__/SicpIndexPage.test.tsx index ae300b4173..4f2fa6bff3 100644 --- a/src/pages/sicp/subcomponents/__tests__/SicpIndexPage.test.tsx +++ b/src/pages/sicp/subcomponents/__tests__/SicpIndexPage.test.tsx @@ -2,11 +2,14 @@ import { MemoryRouter } from 'react-router'; import { renderTreeJson } from 'src/commons/utils/TestUtils'; import SicpIndexPage from '../../subcomponents/SicpIndexPage'; +import { SicpLanguageContextProvider } from '../SicpLanguageProvider'; test('Sicp index page', async () => { const tree = await renderTreeJson( - + + + ); expect(tree).toMatchSnapshot(); diff --git a/src/pages/sicp/subcomponents/__tests__/SicpToc.test.tsx b/src/pages/sicp/subcomponents/__tests__/SicpToc.test.tsx index 73c8bf41a9..791e9d2d6d 100644 --- a/src/pages/sicp/subcomponents/__tests__/SicpToc.test.tsx +++ b/src/pages/sicp/subcomponents/__tests__/SicpToc.test.tsx @@ -1,6 +1,7 @@ import { MemoryRouter } from 'react-router'; import { renderTreeJson } from 'src/commons/utils/TestUtils'; +import { SicpLanguageContextProvider } from '../SicpLanguageProvider'; import SicpToc from '../SicpToc'; test('Sicp toc renders correctly', async () => { @@ -10,7 +11,9 @@ test('Sicp toc renders correctly', async () => { const tree = await renderTreeJson( - + + + ); expect(tree).toMatchSnapshot(); diff --git a/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpIndexPage.test.tsx.snap b/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpIndexPage.test.tsx.snap index b2ec63dffa..0ad1b8fbb4 100644 --- a/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpIndexPage.test.tsx.snap +++ b/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpIndexPage.test.tsx.snap @@ -74,61 +74,631 @@ exports[`Sicp index page 1`] = ` className="sicp-toc" >
    -
    -
    +
    + + + Foreword + +
    +
    +
    +
    + +
  • +
    + + + Foreword to Structure and Interpretation of Computer Programs, 1984 + +
    +
    +
    +
    +
  • +
  • +
    + + + Preface + +
    +
    +
    +
    +
  • +
  • +
    + + + Prefaces to Structure and Interpretation of Computer Programs, 1996 & 1984 + +
    +
    +
    +
    +
  • +
  • +
    + + + Acknowledgments + +
    +
    +
    +
    +
  • +
  • +
    + + + + Expand group + + + + + + 1 Building Abstractions with Functions + +
    +
    +
    +
    +
  • +
  • +
    + + + + Expand group + + + + + + 2 Building Abstractions with Data + +
    +
    +
    +
    +
  • +
  • + + + + Expand group + + + + + + 3 Modularity, Objects, and State + +
    +
    +
    +
  • +
  • +
    + + + Expand group + + + + + 4 Metalinguistic Abstraction + +
    +
    +
    +
    +
  • +
  • +
    + + + + Expand group + -
    + + + 5 Computing with Register Machines + +
  • +
    +
    -
    -
    +
  • -

    - Loading Content -

    -
  • -
    + + + References + +
    +
    +
    +
    + +
  • +
    + + + About the SICP JS Project + +
    +
    +
    +
    +
  • +

    diff --git a/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap b/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap index 0000b2b1cb..7588cee636 100644 --- a/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap +++ b/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap @@ -5,61 +5,631 @@ exports[`Sicp toc renders correctly 1`] = ` className="sicp-toc" >
    -
    -
    +
    + + + Foreword + +
    +
    +
    +
    + +
  • +
    + + + Foreword to Structure and Interpretation of Computer Programs, 1984 + +
    +
    +
    +
    +
  • +
  • +
    + + + Preface + +
    +
    +
    +
    +
  • +
  • +
    + + + Prefaces to Structure and Interpretation of Computer Programs, 1996 & 1984 + +
    +
    +
    +
    +
  • +
  • +
    + + + Acknowledgments + +
    +
    +
    +
    +
  • +
  • +
    + + + + Expand group + + + + + + 1 Building Abstractions with Functions + +
    +
    +
    +
    +
  • +
  • +
    + + + + Expand group + + + + + + 2 Building Abstractions with Data + +
    +
    +
    +
    +
  • +
  • + + + + Expand group + + + + + + 3 Modularity, Objects, and State + +
    +
    +
    +
  • +
  • +
    + + + Expand group + + + + + 4 Metalinguistic Abstraction + +
    +
    +
    +
    +
  • +
  • +
    + + + + Expand group + -
    + + + 5 Computing with Register Machines + +
  • +
    +
    -
    -
    +
  • -

    - Loading Content -

    -
  • -
    + + + References + +
    +
    +
    +
    + +
  • +
    + + + About the SICP JS Project + +
    +
    +
    +
    +
  • +
    `;