Fix library anchor navigation and scroll cleanup
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
This commit is contained in:
parent
99d916f6db
commit
75d4d99a87
4 changed files with 88 additions and 22 deletions
|
|
@ -1,5 +1,5 @@
|
|||
// Rôle : affiche la librairie des outils avec des exemples locaux non persistés.
|
||||
import { Suspense, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Icon } from "../components/Icon.jsx";
|
||||
import { LIBRARY_CATEGORY_DEFINITIONS, getToolboxModuleDefinition } from "../features/toolboxes/toolboxModuleRegistry.js";
|
||||
import { MODULE_COMPONENTS } from "../features/toolboxes/modules/toolboxModuleComponents.jsx";
|
||||
|
|
@ -62,7 +62,11 @@ function getLibraryCategoryAnchorId(category) {
|
|||
|
||||
function getCurrentLibraryAnchor() {
|
||||
const match = location.hash.match(/^#\/library#(.+)$/);
|
||||
return match ? decodeURIComponent(match[1]) : "";
|
||||
try {
|
||||
return match ? decodeURIComponent(match[1]) : "";
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getLibraryAnchorOffset() {
|
||||
|
|
@ -71,17 +75,20 @@ function getLibraryAnchorOffset() {
|
|||
return topbarHeight + 16;
|
||||
}
|
||||
|
||||
function scrollToLibraryTool(anchorId, replace = false, behavior = "smooth") {
|
||||
if (!anchorId) return;
|
||||
if (replace) history.replaceState(null, "", `#/library#${encodeURIComponent(anchorId)}`);
|
||||
LIBRARY_ANCHOR_RETRY_DELAYS.forEach((delay, index) => {
|
||||
window.setTimeout(() => {
|
||||
function scrollToLibraryTool(anchorId, behavior) {
|
||||
let timers = [];
|
||||
const frame = requestAnimationFrame(() => {
|
||||
timers = LIBRARY_ANCHOR_RETRY_DELAYS.map((delay, index) => window.setTimeout(() => {
|
||||
const element = document.getElementById(anchorId);
|
||||
if (!element) return;
|
||||
const top = element.getBoundingClientRect().top + window.scrollY - getLibraryAnchorOffset();
|
||||
window.scrollTo({ top: Math.max(0, top), behavior: index === 0 ? behavior : "auto" });
|
||||
}, delay);
|
||||
}, delay));
|
||||
});
|
||||
return () => {
|
||||
cancelAnimationFrame(frame);
|
||||
timers.forEach((timer) => window.clearTimeout(timer));
|
||||
};
|
||||
}
|
||||
|
||||
function findCategoryForAnchor(categories, anchorId) {
|
||||
|
|
@ -114,9 +121,13 @@ export function LibraryPage({ siteContent, actions }) {
|
|||
const [libraryPayload, setLibraryPayload] = useState(null);
|
||||
const [libraryError, setLibraryError] = useState("");
|
||||
const [moduleData, setModuleData] = useState({});
|
||||
const [activeCategoryKey, setActiveCategoryKey] = useState("");
|
||||
const [navigation, setNavigation] = useState(() => ({ anchorId: getCurrentLibraryAnchor(), behavior: "auto" }));
|
||||
const libraryCategories = useMemo(() => getLibraryCategories(libraryPayload?.toolbox?.modules || [], content), [content, libraryPayload]);
|
||||
const activeCategory = libraryCategories.find((category) => category.key === activeCategoryKey) || libraryCategories[0] || null;
|
||||
const activeCategory = findCategoryForAnchor(libraryCategories, navigation.anchorId) || libraryCategories[0] || null;
|
||||
const selectAnchor = useCallback((anchorId) => {
|
||||
history.replaceState(null, "", `#/library#${encodeURIComponent(anchorId)}`);
|
||||
setNavigation({ anchorId, behavior: "smooth" });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
|
@ -137,13 +148,15 @@ export function LibraryPage({ siteContent, actions }) {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!libraryPayload || !libraryCategories.length) return;
|
||||
const anchorId = getCurrentLibraryAnchor();
|
||||
const category = findCategoryForAnchor(libraryCategories, anchorId) || libraryCategories[0];
|
||||
setActiveCategoryKey(category.key);
|
||||
if (!anchorId) return;
|
||||
requestAnimationFrame(() => scrollToLibraryTool(anchorId, false, "auto"));
|
||||
}, [libraryPayload, libraryCategories]);
|
||||
const onHashChange = () => setNavigation({ anchorId: getCurrentLibraryAnchor(), behavior: "auto" });
|
||||
window.addEventListener("hashchange", onHashChange);
|
||||
return () => window.removeEventListener("hashchange", onHashChange);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeCategory?.key || !navigation.anchorId) return undefined;
|
||||
return scrollToLibraryTool(navigation.anchorId, navigation.behavior);
|
||||
}, [activeCategory?.key, navigation]);
|
||||
|
||||
const moduleContext = useMemo(() => ({
|
||||
getModuleData: (toolboxId, moduleId, fallback) => moduleData[`${toolboxId}:${moduleId}`] || fallback,
|
||||
|
|
@ -199,9 +212,9 @@ export function LibraryPage({ siteContent, actions }) {
|
|||
...current,
|
||||
[`${LIBRARY_TOOLBOX_ID}:${annotationModule.id}`]: { image: dataUrl, markers: [], drawings: { strokes: [] } }
|
||||
}));
|
||||
requestAnimationFrame(() => scrollToLibraryTool(getLibraryAnchorId(annotationModule), true));
|
||||
selectAnchor(getLibraryAnchorId(annotationModule));
|
||||
}
|
||||
}), [actions, libraryPayload, moduleData, siteContent.toolboxes.modules]);
|
||||
}), [actions, libraryPayload, moduleData, selectAnchor, siteContent.toolboxes.modules]);
|
||||
|
||||
return (
|
||||
<div className="library-page">
|
||||
|
|
@ -235,8 +248,7 @@ export function LibraryPage({ siteContent, actions }) {
|
|||
categories={libraryCategories}
|
||||
activeCategoryKey={activeCategory?.key || ""}
|
||||
onSelectCategory={(category, anchorId = getLibraryCategoryAnchorId(category)) => {
|
||||
setActiveCategoryKey(category.key);
|
||||
requestAnimationFrame(() => scrollToLibraryTool(anchorId, true));
|
||||
selectAnchor(anchorId);
|
||||
}}
|
||||
/>
|
||||
<section className="library-tools" aria-label={content.toolsLabel}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue