From 5b990d861bfc86d0312556c611ecba41843343af Mon Sep 17 00:00:00 2001 From: yihao Date: Wed, 14 May 2025 22:32:22 +0800 Subject: [PATCH 01/10] 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/10] 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/10] 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/10] 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/10] 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 }} >