Merge branch 'improve-property-tooltip' into loginPage

This commit is contained in:
choizhang
2025-03-15 00:11:50 +08:00
57 changed files with 1822 additions and 606 deletions

View File

@@ -1,5 +1,40 @@
import { useState, useEffect } from 'react'
import { useTabVisibility } from '@/contexts/useTabVisibility'
import { backendBaseUrl } from '@/lib/constants'
import { useTranslation } from 'react-i18next'
export default function ApiSite() {
return <iframe src={backendBaseUrl + '/docs'} className="size-full" />
const { t } = useTranslation()
const { isTabVisible } = useTabVisibility()
const isApiTabVisible = isTabVisible('api')
const [iframeLoaded, setIframeLoaded] = useState(false)
// Load the iframe once on component mount
useEffect(() => {
if (!iframeLoaded) {
setIframeLoaded(true)
}
}, [iframeLoaded])
// Use CSS to hide content when tab is not visible
return (
<div className={`size-full ${isApiTabVisible ? '' : 'hidden'}`}>
{iframeLoaded ? (
<iframe
src={backendBaseUrl + '/docs'}
className="size-full w-full h-full"
style={{ width: '100%', height: '100%', border: 'none' }}
// Use key to ensure iframe doesn't reload
key="api-docs-iframe"
/>
) : (
<div className="flex h-full w-full items-center justify-center bg-background">
<div className="text-center">
<div className="mb-2 h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"></div>
<p>{t('apiSite.loading')}</p>
</div>
</div>
)}
</div>
)
}

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { useState, useEffect, useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { useTabVisibility } from '@/contexts/useTabVisibility'
import Button from '@/components/ui/Button'
import {
Table,
@@ -26,6 +27,9 @@ export default function DocumentManager() {
const { t } = useTranslation()
const health = useBackendState.use.health()
const [docs, setDocs] = useState<DocsStatusesResponse | null>(null)
const { isTabVisible } = useTabVisibility()
const isDocumentsTabVisible = isTabVisible('documents')
const initialLoadRef = useRef(false)
const fetchDocuments = useCallback(async () => {
try {
@@ -48,11 +52,15 @@ export default function DocumentManager() {
} catch (err) {
toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) }))
}
}, [setDocs])
}, [setDocs, t])
// Only fetch documents when the tab becomes visible for the first time
useEffect(() => {
fetchDocuments()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
if (isDocumentsTabVisible && !initialLoadRef.current) {
fetchDocuments()
initialLoadRef.current = true
}
}, [isDocumentsTabVisible, fetchDocuments])
const scanDocuments = useCallback(async () => {
try {
@@ -61,21 +69,24 @@ export default function DocumentManager() {
} catch (err) {
toast.error(t('documentPanel.documentManager.errors.scanFailed', { error: errorMessage(err) }))
}
}, [])
}, [t])
// Only set up polling when the tab is visible and health is good
useEffect(() => {
if (!isDocumentsTabVisible || !health) {
return
}
const interval = setInterval(async () => {
if (!health) {
return
}
try {
await fetchDocuments()
} catch (err) {
toast.error(t('documentPanel.documentManager.errors.scanProgressFailed', { error: errorMessage(err) }))
}
}, 5000)
return () => clearInterval(interval)
}, [health, fetchDocuments])
}, [health, fetchDocuments, t, isDocumentsTabVisible])
return (
<Card className="!size-full !rounded-none !border-none">

View File

@@ -1,4 +1,5 @@
import { useEffect, useState, useCallback, useMemo } from 'react'
import { useEffect, useState, useCallback, useMemo, useRef } from 'react'
import { useTabVisibility } from '@/contexts/useTabVisibility'
// import { MiniMap } from '@react-sigma/minimap'
import { SigmaContainer, useRegisterEvents, useSigma } from '@react-sigma/core'
import { Settings as SigmaSettings } from 'sigma/settings'
@@ -17,6 +18,7 @@ import Settings from '@/components/graph/Settings'
import GraphSearch from '@/components/graph/GraphSearch'
import GraphLabels from '@/components/graph/GraphLabels'
import PropertiesView from '@/components/graph/PropertiesView'
import SettingsDisplay from '@/components/graph/SettingsDisplay'
import { useSettingsStore } from '@/stores/settings'
import { useGraphStore } from '@/stores/graph'
@@ -90,8 +92,12 @@ const GraphEvents = () => {
}
},
// Disable the autoscale at the first down interaction
mousedown: () => {
if (!sigma.getCustomBBox()) sigma.setCustomBBox(sigma.getBBox())
mousedown: (e) => {
// Only set custom BBox if it's a drag operation (mouse button is pressed)
const mouseEvent = e.original as MouseEvent;
if (mouseEvent.buttons !== 0 && !sigma.getCustomBBox()) {
sigma.setCustomBBox(sigma.getBBox())
}
}
})
}, [registerEvents, sigma, draggedNode])
@@ -101,27 +107,46 @@ const GraphEvents = () => {
const GraphViewer = () => {
const [sigmaSettings, setSigmaSettings] = useState(defaultSigmaSettings)
const sigmaRef = useRef<any>(null)
const initAttemptedRef = useRef(false)
const selectedNode = useGraphStore.use.selectedNode()
const focusedNode = useGraphStore.use.focusedNode()
const moveToSelectedNode = useGraphStore.use.moveToSelectedNode()
const isFetching = useGraphStore.use.isFetching()
const shouldRender = useGraphStore.use.shouldRender() // Rendering control state
// Get tab visibility
const { isTabVisible } = useTabVisibility()
const isGraphTabVisible = isTabVisible('knowledge-graph')
const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
const renderLabels = useSettingsStore.use.showNodeLabel()
const enableEdgeEvents = useSettingsStore.use.enableEdgeEvents()
const enableNodeDrag = useSettingsStore.use.enableNodeDrag()
const renderEdgeLabels = useSettingsStore.use.showEdgeLabel()
// Handle component mount/unmount and tab visibility
useEffect(() => {
setSigmaSettings({
...defaultSigmaSettings,
enableEdgeEvents,
renderEdgeLabels,
renderLabels
})
}, [renderLabels, enableEdgeEvents, renderEdgeLabels])
// When component mounts or tab becomes visible
if (isGraphTabVisible && !shouldRender && !isFetching && !initAttemptedRef.current) {
// If tab is visible but graph is not rendering, try to enable rendering
useGraphStore.getState().setShouldRender(true)
initAttemptedRef.current = true
console.log('Graph viewer initialized')
}
// Cleanup function when component unmounts
return () => {
// Only log cleanup, don't actually clean up the WebGL context
// This allows the WebGL context to persist across tab switches
console.log('Graph viewer cleanup')
}
}, [isGraphTabVisible, shouldRender, isFetching])
// Initialize sigma settings once on component mount
// All dynamic settings will be updated in GraphControl using useSetSettings
useEffect(() => {
setSigmaSettings(defaultSigmaSettings)
}, [])
const onSearchFocus = useCallback((value: GraphSearchOption | null) => {
if (value === null) useGraphStore.getState().setFocusedNode(null)
@@ -142,43 +167,73 @@ const GraphViewer = () => {
[selectedNode]
)
// Since TabsContent now forces mounting of all tabs, we need to conditionally render
// the SigmaContainer based on visibility to avoid unnecessary rendering
return (
<SigmaContainer settings={sigmaSettings} className="!bg-background !size-full overflow-hidden">
<GraphControl />
<div className="relative h-full w-full">
{/* Only render the SigmaContainer when the tab is visible */}
{isGraphTabVisible ? (
<SigmaContainer
settings={sigmaSettings}
className="!bg-background !size-full overflow-hidden"
ref={sigmaRef}
>
<GraphControl />
{enableNodeDrag && <GraphEvents />}
{enableNodeDrag && <GraphEvents />}
<FocusOnNode node={autoFocusedNode} move={moveToSelectedNode} />
<FocusOnNode node={autoFocusedNode} move={moveToSelectedNode} />
<div className="absolute top-2 left-2 flex items-start gap-2">
<GraphLabels />
{showNodeSearchBar && (
<GraphSearch
value={searchInitSelectedNode}
onFocus={onSearchFocus}
onChange={onSearchSelect}
/>
)}
</div>
<div className="absolute top-2 left-2 flex items-start gap-2">
<GraphLabels />
{showNodeSearchBar && (
<GraphSearch
value={searchInitSelectedNode}
onFocus={onSearchFocus}
onChange={onSearchSelect}
/>
)}
</div>
<div className="bg-background/60 absolute bottom-2 left-2 flex flex-col rounded-xl border-2 backdrop-blur-lg">
<Settings />
<ZoomControl />
<LayoutsControl />
<FullScreenControl />
{/* <ThemeToggle /> */}
</div>
<div className="bg-background/60 absolute bottom-2 left-2 flex flex-col rounded-xl border-2 backdrop-blur-lg">
<Settings />
<ZoomControl />
<LayoutsControl />
<FullScreenControl />
{/* <ThemeToggle /> */}
</div>
{showPropertyPanel && (
<div className="absolute top-2 right-2">
<PropertiesView />
{showPropertyPanel && (
<div className="absolute top-2 right-2">
<PropertiesView />
</div>
)}
{/* <div className="absolute bottom-2 right-2 flex flex-col rounded-xl border-2">
<MiniMap width="100px" height="100px" />
</div> */}
<SettingsDisplay />
</SigmaContainer>
) : (
// Placeholder when tab is not visible
<div className="flex h-full w-full items-center justify-center">
<div className="text-center text-muted-foreground">
{/* Placeholder content */}
</div>
</div>
)}
{/* <div className="absolute bottom-2 right-2 flex flex-col rounded-xl border-2">
<MiniMap width="100px" height="100px" />
</div> */}
</SigmaContainer>
{/* Loading overlay - shown when data is loading */}
{isFetching && (
<div className="absolute inset-0 flex items-center justify-center bg-background/80 z-10">
<div className="text-center">
<div className="mb-2 h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"></div>
<p>Loading Graph Data...</p>
</div>
</div>
)}
</div>
)
}

View File

@@ -1,6 +1,6 @@
import Button from '@/components/ui/Button'
import { SiteInfo } from '@/lib/constants'
import ThemeToggle from '@/components/ThemeToggle'
import AppSettings from '@/components/AppSettings'
import LanguageToggle from '@/components/LanguageToggle'
import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
import { useSettingsStore } from '@/stores/settings'
@@ -77,23 +77,15 @@ export default function SiteHeader() {
<TabsNavigation />
</div>
<nav className="flex items-center gap-2">
<Button variant="ghost" size="icon" side="bottom" tooltip={t('header.projectRepository')}>
<a href={SiteInfo.github} target="_blank" rel="noopener noreferrer">
<GithubIcon className="size-4" aria-hidden="true" />
</a>
</Button>
<LanguageToggle />
<ThemeToggle />
<Button
variant="ghost"
size="icon"
side="bottom"
tooltip="Log Out"
onClick={handleLogout}
>
<LogOutIcon className="size-4" aria-hidden="true" />
</Button>
<nav className="flex items-center">
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" side="bottom" tooltip={t('header.projectRepository')}>
<a href={SiteInfo.github} target="_blank" rel="noopener noreferrer">
<GithubIcon className="size-4" aria-hidden="true" />
</a>
</Button>
<AppSettings />
</div>
</nav>
</header>
)