Use zustand for state management
This commit is contained in:
36
frontend/package-lock.json
generated
36
frontend/package-lock.json
generated
@@ -12,7 +12,8 @@
|
|||||||
"@tanstack/react-query-devtools": "^5.74.6",
|
"@tanstack/react-query-devtools": "^5.74.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^7.5.1"
|
"react-router-dom": "^7.5.1",
|
||||||
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.22.0",
|
"@eslint/js": "^9.22.0",
|
||||||
@@ -1471,7 +1472,7 @@
|
|||||||
"version": "19.1.2",
|
"version": "19.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz",
|
||||||
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
|
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -1963,7 +1964,7 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
@@ -3436,6 +3437,35 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zustand": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": ">=18.0.0",
|
||||||
|
"immer": ">=9.0.6",
|
||||||
|
"react": ">=18.0.0",
|
||||||
|
"use-sync-external-store": ">=1.2.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"immer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"use-sync-external-store": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
"@tanstack/react-query-devtools": "^5.74.6",
|
"@tanstack/react-query-devtools": "^5.74.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^7.5.1"
|
"react-router-dom": "^7.5.1",
|
||||||
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.22.0",
|
"@eslint/js": "^9.22.0",
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import Panel from "./components/Panel";
|
|
||||||
|
|
||||||
import { useQueries } from "@tanstack/react-query";
|
import { useQueries } from "@tanstack/react-query";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import "./App.css";
|
|
||||||
import ArticleSelector from "./components/ArticleSelector";
|
|
||||||
import TOC from "./components/TOC";
|
|
||||||
import { getArticleIds, getToc } from "./lib/api";
|
import { getArticleIds, getToc } from "./lib/api";
|
||||||
import { Language } from "./lib/types";
|
import { Language } from "./lib/types";
|
||||||
|
|
||||||
|
import ArticleSelector from "./components/ArticleSelector";
|
||||||
|
import Panel from "./components/Panel";
|
||||||
|
import TOC from "./components/TOC";
|
||||||
|
|
||||||
|
import useUIStore from "./store/uiStore";
|
||||||
|
|
||||||
|
import "./App.css";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
celexId: string;
|
celexId: string;
|
||||||
articleId: number;
|
articleId: number;
|
||||||
@@ -16,10 +19,7 @@ type Props = {
|
|||||||
|
|
||||||
function App({ celexId, articleId }: Props) {
|
function App({ celexId, articleId }: Props) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [numPanels, setNumPanels] = useState(1);
|
const { numPanels, addPanel } = useUIStore();
|
||||||
const [selectedParagraphId, setSelectedParagraphId] = useState<string | null>(
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
const results = useQueries({
|
const results = useQueries({
|
||||||
queries: [
|
queries: [
|
||||||
@@ -76,9 +76,7 @@ function App({ celexId, articleId }: Props) {
|
|||||||
selectedId={articleId}
|
selectedId={articleId}
|
||||||
onSelected={(id) => navigate(`/${celexId}/articles/${id}`)}
|
onSelected={(id) => navigate(`/${celexId}/articles/${id}`)}
|
||||||
/>
|
/>
|
||||||
<button onClick={() => setNumPanels((prev) => prev + 1)}>
|
<button onClick={addPanel}>Add Panel</button>
|
||||||
Add Panel
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-container">
|
<div className="panel-container">
|
||||||
<TOC
|
<TOC
|
||||||
@@ -94,8 +92,6 @@ function App({ celexId, articleId }: Props) {
|
|||||||
Object.values(Language)[index % Object.values(Language).length]
|
Object.values(Language)[index % Object.values(Language).length]
|
||||||
}
|
}
|
||||||
articleId={articleId}
|
articleId={articleId}
|
||||||
selectedParagraphId={selectedParagraphId || undefined}
|
|
||||||
onParagraphSelected={setSelectedParagraphId}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { getArticle } from "../lib/api";
|
import { getArticle } from "../lib/api";
|
||||||
import { Language } from "../lib/types";
|
import { Language } from "../lib/types";
|
||||||
|
import useUIStore from "../store/uiStore";
|
||||||
import LanguageSwitcher from "./LanguageSwitcher";
|
import LanguageSwitcher from "./LanguageSwitcher";
|
||||||
import "./Panel.css";
|
import "./Panel.css";
|
||||||
|
|
||||||
@@ -9,17 +10,11 @@ type PanelProps = {
|
|||||||
celexId: string;
|
celexId: string;
|
||||||
language?: Language;
|
language?: Language;
|
||||||
articleId: number;
|
articleId: number;
|
||||||
selectedParagraphId?: string;
|
|
||||||
onParagraphSelected(paragraphId: string): void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function Panel({
|
function Panel({ celexId, language, articleId }: PanelProps) {
|
||||||
celexId,
|
const { selectedParagraphId, setSelectedParagraphId } = useUIStore();
|
||||||
language,
|
|
||||||
articleId,
|
|
||||||
onParagraphSelected,
|
|
||||||
selectedParagraphId,
|
|
||||||
}: PanelProps) {
|
|
||||||
const [lang, setLang] = useState(language || Language.ENG);
|
const [lang, setLang] = useState(language || Language.ENG);
|
||||||
const articleRef = useRef<HTMLDivElement>(null);
|
const articleRef = useRef<HTMLDivElement>(null);
|
||||||
const { data, isPending, error } = useQuery({
|
const { data, isPending, error } = useQuery({
|
||||||
@@ -39,11 +34,11 @@ function Panel({
|
|||||||
p.classList.remove("highlight");
|
p.classList.remove("highlight");
|
||||||
});
|
});
|
||||||
if (selectedParagraphId) {
|
if (selectedParagraphId) {
|
||||||
const selectedParagraph = Array.from(paragraphs).find(
|
const el = Array.from(paragraphs).find(
|
||||||
(p) => p.getAttribute("data-paragraph-id") === selectedParagraphId
|
(p) => p.getAttribute("data-paragraph-id") === selectedParagraphId
|
||||||
);
|
);
|
||||||
if (selectedParagraph) {
|
if (el) {
|
||||||
selectedParagraph.classList.add("highlight");
|
el.classList.add("highlight");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +53,7 @@ function Panel({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
target.classList.add("highlight");
|
target.classList.add("highlight");
|
||||||
onParagraphSelected(paragraphId);
|
setSelectedParagraphId(paragraphId);
|
||||||
};
|
};
|
||||||
|
|
||||||
paragraphs.forEach((element) => {
|
paragraphs.forEach((element) => {
|
||||||
@@ -72,7 +67,7 @@ function Panel({
|
|||||||
element.removeEventListener("click", handleClick(element));
|
element.removeEventListener("click", handleClick(element));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}, [articleRef, data, selectedParagraphId, onParagraphSelected]);
|
}, [articleRef, data, selectedParagraphId, setSelectedParagraphId]);
|
||||||
|
|
||||||
if (isPending) return "Loading...";
|
if (isPending) return "Loading...";
|
||||||
if (error) return "An error has occurred: " + error.message;
|
if (error) return "An error has occurred: " + error.message;
|
||||||
|
|||||||
21
frontend/src/store/uiStore.ts
Normal file
21
frontend/src/store/uiStore.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
interface UIState {
|
||||||
|
numPanels: number;
|
||||||
|
addPanel: () => void;
|
||||||
|
removePanel: () => void;
|
||||||
|
|
||||||
|
selectedParagraphId: string | null;
|
||||||
|
setSelectedParagraphId: (selectedParagraphId: string | null) => void;
|
||||||
|
}
|
||||||
|
const useUIStore = create<UIState>((set) => ({
|
||||||
|
numPanels: 1,
|
||||||
|
selectedParagraphId: null,
|
||||||
|
addPanel: () => set((state) => ({ numPanels: state.numPanels + 1 })),
|
||||||
|
removePanel: () =>
|
||||||
|
set((state) => ({ numPanels: Math.max(state.numPanels - 1, 1) })),
|
||||||
|
setSelectedParagraphId: (selectedParagraphId: string | null) =>
|
||||||
|
set({ selectedParagraphId }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useUIStore;
|
||||||
Reference in New Issue
Block a user