mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-08-02 17:11:48 +00:00
Extract useWebMcpBridge from app.tsx
This commit is contained in:
@@ -86,8 +86,8 @@ To refactor `app.tsx` safely and maintain continuous code correctness, we will m
|
|||||||
* [x] **Step 1.4:** Modify `src/menu/top_bar.tsx` to make chart-specific props and event handlers optional (e.g. `data`, `allowAllRelativesChart`, `allowPrintAndDownload`, `eventHandlers`, etc.), preparing the component for rendering on the landing screen without dummy properties.
|
* [x] **Step 1.4:** Modify `src/menu/top_bar.tsx` to make chart-specific props and event handlers optional (e.g. `data`, `allowAllRelativesChart`, `allowPrintAndDownload`, `eventHandlers`, etc.), preparing the component for rendering on the landing screen without dummy properties.
|
||||||
|
|
||||||
#### Phase 2: WebMCP Bridge Extraction
|
#### Phase 2: WebMCP Bridge Extraction
|
||||||
* [ ] **Step 2.1:** Create `src/hooks/use_webmcp_bridge.ts` wrapping WebMCP registration and synchronization effects.
|
* [x] **Step 2.1:** Create `src/hooks/use_webmcp_bridge.ts` wrapping WebMCP registration and synchronization effects.
|
||||||
* [ ] **Step 2.2:** Replace the inline WebMCP logic and `useEffect` blocks in `src/app.tsx` with a single call to the custom `useWebMcpBridge` hook. Verify using `npm test`. Note: During Phase 4, this hook call will be moved from `src/app.tsx` into `src/pages/view_page.tsx` where the layout state is relocated.
|
* [x] **Step 2.2:** Replace the inline WebMCP logic and `useEffect` blocks in `src/app.tsx` with a single call to the custom `useWebMcpBridge` hook. Verify using `npm test`. Note: During Phase 4, this hook call will be moved from `src/app.tsx` into `src/pages/view_page.tsx` where the layout state is relocated.
|
||||||
|
|
||||||
#### Phase 3: Shifting to URL as Single Source of Truth
|
#### Phase 3: Shifting to URL as Single Source of Truth
|
||||||
We will gradually eliminate React state variables in `src/app.tsx` and derive them (including settings like `standalone`, `showWikiTreeMenus`, and `freezeAnimation`) directly from the `useLocation()` search query parameters on render:
|
We will gradually eliminate React state variables in `src/app.tsx` and derive them (including settings like `standalone`, `showWikiTreeMenus`, and `freezeAnimation`) directly from the `useLocation()` search query parameters on render:
|
||||||
|
|||||||
26
src/app.tsx
26
src/app.tsx
@@ -43,6 +43,7 @@ import {
|
|||||||
WikiTreeSourceSpec,
|
WikiTreeSourceSpec,
|
||||||
} from './datasource/wikitree';
|
} from './datasource/wikitree';
|
||||||
import {DonatsoChart} from './donatso-chart';
|
import {DonatsoChart} from './donatso-chart';
|
||||||
|
import {useWebMcpBridge} from './hooks/use_webmcp_bridge';
|
||||||
import {Intro} from './intro';
|
import {Intro} from './intro';
|
||||||
import {GoogleAuthModal} from './menu/google_auth_modal';
|
import {GoogleAuthModal} from './menu/google_auth_modal';
|
||||||
import {TopBar} from './menu/top_bar';
|
import {TopBar} from './menu/top_bar';
|
||||||
@@ -64,7 +65,6 @@ import {
|
|||||||
getStaticUrl,
|
getStaticUrl,
|
||||||
getUrlForArgs,
|
getUrlForArgs,
|
||||||
} from './util/url_args';
|
} from './util/url_args';
|
||||||
import {WebMcpBridge} from './webmcp';
|
|
||||||
|
|
||||||
const staticUrl = getStaticUrl();
|
const staticUrl = getStaticUrl();
|
||||||
|
|
||||||
@@ -108,8 +108,7 @@ export function App() {
|
|||||||
const [freezeAnimation, setFreezeAnimation] = useState(false);
|
const [freezeAnimation, setFreezeAnimation] = useState(false);
|
||||||
/** Configuration settings for chart display options (e.g. colors, hiding IDs). */
|
/** Configuration settings for chart display options (e.g. colors, hiding IDs). */
|
||||||
const [config, setConfig] = useState(DEFALUT_CONFIG);
|
const [config, setConfig] = useState(DEFALUT_CONFIG);
|
||||||
/** MCP bridge to communicate with external tools or servers (Model Context Protocol). */
|
|
||||||
const [mcpBridge] = useState(() => new WebMcpBridge());
|
|
||||||
/** Controls the visibility of the Google Drive OAuth permission modal. */
|
/** Controls the visibility of the Google Drive OAuth permission modal. */
|
||||||
const [showAuthModal, setShowAuthModal] = useState(false);
|
const [showAuthModal, setShowAuthModal] = useState(false);
|
||||||
/** Stores the file ID that failed to load from Google Drive due to authorization errors. */
|
/** Stores the file ID that failed to load from Google Drive due to authorization errors. */
|
||||||
@@ -456,13 +455,6 @@ export function App() {
|
|||||||
updateDisplay,
|
updateDisplay,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
mcpBridge.registerTools();
|
|
||||||
return () => {
|
|
||||||
mcpBridge.unregisterTools();
|
|
||||||
};
|
|
||||||
}, [mcpBridge]);
|
|
||||||
|
|
||||||
// Clean up object URLs created for uploaded images/files when the dataset
|
// Clean up object URLs created for uploaded images/files when the dataset
|
||||||
// changes or the app unmounts to prevent memory leaks.
|
// changes or the app unmounts to prevent memory leaks.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -471,19 +463,7 @@ export function App() {
|
|||||||
};
|
};
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useWebMcpBridge(data, detailIndi, onSelection);
|
||||||
mcpBridge.setData(data || null);
|
|
||||||
}, [data, mcpBridge]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
mcpBridge.setDetailIndi(detailIndi || null);
|
|
||||||
}, [detailIndi, mcpBridge]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
mcpBridge.setSetSelectionCallback((id: string) => {
|
|
||||||
onSelection({id, generation: 0});
|
|
||||||
});
|
|
||||||
}, [mcpBridge, location]);
|
|
||||||
|
|
||||||
function updateUrl(
|
function updateUrl(
|
||||||
args: Record<string, string | (string | null)[] | null | undefined>,
|
args: Record<string, string | (string | null)[] | null | undefined>,
|
||||||
|
|||||||
49
src/hooks/use_webmcp_bridge.ts
Normal file
49
src/hooks/use_webmcp_bridge.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import {useEffect, useRef, useState} from 'react';
|
||||||
|
import {IndiInfo} from 'topola';
|
||||||
|
import {TopolaData} from '../util/gedcom_util';
|
||||||
|
import {WebMcpBridge} from '../webmcp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to manage the lifecycle and synchronization of the WebMCP bridge.
|
||||||
|
* It instantiates the WebMcpBridge and coordinates tool registration, data updates,
|
||||||
|
* detail selection updates, and selection callbacks.
|
||||||
|
*/
|
||||||
|
export function useWebMcpBridge(
|
||||||
|
data: TopolaData | undefined | null,
|
||||||
|
detailIndi: string | undefined | null,
|
||||||
|
onSelection: (selection: IndiInfo) => void,
|
||||||
|
) {
|
||||||
|
const [mcpBridge] = useState(() => new WebMcpBridge());
|
||||||
|
|
||||||
|
// Store the onSelection callback in a ref to prevent recreating the selection callback effect
|
||||||
|
// when onSelection changes.
|
||||||
|
const onSelectionRef = useRef(onSelection);
|
||||||
|
useEffect(() => {
|
||||||
|
onSelectionRef.current = onSelection;
|
||||||
|
}, [onSelection]);
|
||||||
|
|
||||||
|
// Handle registration and cleanup of WebMCP tools.
|
||||||
|
useEffect(() => {
|
||||||
|
mcpBridge.registerTools();
|
||||||
|
return () => {
|
||||||
|
mcpBridge.unregisterTools();
|
||||||
|
};
|
||||||
|
}, [mcpBridge]);
|
||||||
|
|
||||||
|
// Synchronize the active dataset with the bridge.
|
||||||
|
useEffect(() => {
|
||||||
|
mcpBridge.setData(data || null);
|
||||||
|
}, [data, mcpBridge]);
|
||||||
|
|
||||||
|
// Synchronize the currently selected individual for details display with the bridge.
|
||||||
|
useEffect(() => {
|
||||||
|
mcpBridge.setDetailIndi(detailIndi || null);
|
||||||
|
}, [detailIndi, mcpBridge]);
|
||||||
|
|
||||||
|
// Register the viewport navigation callback with the bridge.
|
||||||
|
useEffect(() => {
|
||||||
|
mcpBridge.setSetSelectionCallback((id: string) => {
|
||||||
|
onSelectionRef.current({id, generation: 0});
|
||||||
|
});
|
||||||
|
}, [mcpBridge]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user